Skip to content

Commit

Permalink
Merge pull request #9 from nodedge/feature/generate-code
Browse files Browse the repository at this point in the history
Feature/generate code
  • Loading branch information
nodedge committed May 30, 2021
2 parents 58f91ac + d28d8c8 commit 5658269
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 16 deletions.
2 changes: 1 addition & 1 deletion examples/calculator/main_code_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
orderedNodeList, currentSceneCode = coder.generateCode()
print(currentSceneCode)

generatedFileString = coder.createFileFromGeneratedCode(
generatedFileString = coder.addImports(
orderedNodeList, currentSceneCode
)

Expand Down
8 changes: 7 additions & 1 deletion nodedge/editor_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from nodedge.application_styler import ApplicationStyler
from nodedge.editor_widget import EditorWidget
from nodedge.scene_coder import SceneCoder


class EditorWindow(QMainWindow):
Expand Down Expand Up @@ -546,7 +547,12 @@ def onFitInView(self):

def onGenerateCode(self):
if self.currentEditorWidget is not None:
self.currentEditorWidget.scene.coder.generateCode()
coder: SceneCoder = self.currentEditorWidget.scene.coder
if coder is not None:
self.currentEditorWidget.scene.coder.generateCodeAndSave()
successStr = f"File saved to {coder.filename}"
self.__logger.debug(successStr)
self.statusBar().showMessage(successStr, 5000)

def createAction(
self,
Expand Down
92 changes: 82 additions & 10 deletions nodedge/scene_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
Scene Coder module containing :class:`~nodedge.scene_coder.SceneCoder` class.
"""
import logging
from pathlib import Path
from typing import List, Tuple

from PySide2.QtCore import QObject, Signal
from PySide2.QtWidgets import QFileDialog

from nodedge.blocks.block_config import OP_NODE_OUTPUT
from nodedge.connector import Socket
from nodedge.node import Node
from nodedge.utils import indentCode


class SceneCoder(QObject):
Expand All @@ -18,11 +21,19 @@ class SceneCoder(QObject):
notConnectedSocket = Signal()

def __init__(self, scene: "Scene", parent=None): # type: ignore
super().__init__(parent)
self.scene = scene
self.filename: str = ""

self.__logger = logging.getLogger(__file__)
self.__logger.setLevel(logging.INFO)
super().__init__(parent)

def generateCodeAndSave(self):

orderedNodeList, generatedCode = self.generateCode()
generatedFileString = self.addImports(orderedNodeList, generatedCode)
self.saveFileAs(generatedFileString)
return

def generateCode(self) -> Tuple[List[Node], str]:
"""
Expand Down Expand Up @@ -90,7 +101,7 @@ def generateCode(self) -> Tuple[List[Node], str]:

return orderedNodeList, generatedCode

def createFileFromGeneratedCode(self, orderedNodeList, generatedCode):
def addImports(self, orderedNodeList, generatedCode):
# add imports on top
importedLibraries = {}
for currentVarIndex, node in enumerate(orderedNodeList):
Expand All @@ -107,8 +118,7 @@ def createFileFromGeneratedCode(self, orderedNodeList, generatedCode):
generatedImport += "\n\n"

# put code into a function
functionName = self.scene.filename.split("/")[-1]
functionName = functionName.split(".")[0].lower()
functionName = self._getFunctionName()
generatedFunctionDef = f"def {functionName}():"
generatedFunctionCall = (
f"\n\n\nif __name__ == '__main__':\n {functionName}()\n"
Expand All @@ -117,12 +127,51 @@ def createFileFromGeneratedCode(self, orderedNodeList, generatedCode):
outputFileString = (
generatedImport
+ generatedFunctionDef
+ _indent_code(generatedCode)
+ indentCode(generatedCode)
+ generatedFunctionCall
)

return outputFileString

def saveFileAs(self, outputFileString):
"""
Choose the filename and path where to save Python generated code via a ``QFileDialog``.
"""
self.__logger.debug("Saving generated code to file")

# define default file name
functionName = self._getFunctionName()
defaultFilename = functionName + ".py"

# open QFileDialog
dialog: QFileDialog = QFileDialog()
# dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) # Set save mode: Ubuntu 18.04 shows "Open"
self.filename, _ = dialog.getSaveFileName(
parent=None,
caption="Save code to file",
dir=str(Path(SceneCoder.getFileDialogDirectory(), defaultFilename)),
filter=SceneCoder.getFileDialogFilter(),
)

# if filename is empty, do nothing
if self.filename == "":
return

# if filename is not empty, save to file
self.saveFile(self.filename, outputFileString)

return

def saveFile(self, filename, outputFileString):
"""
Save Python generated code to file.
"""
with open(filename, "w") as file:
file.write(outputFileString)
self.__logger.debug(f"Saving to {filename} was successful.")

return

def _appendHierarchyUntilRoot(
self, currentNode: Node, appendedNodes: List[Node], nodesToAdd: List[Node]
):
Expand All @@ -134,9 +183,32 @@ def _appendHierarchyUntilRoot(
self._appendHierarchyUntilRoot(parent, appendedNodes, nodesToAdd)
return nodesToAdd

def _getFunctionName(self):
filename = self.scene.filename
if filename is None:
functionName = "unnamed"
else:
functionName = self.scene.filename.split("/")[-1]
functionName = functionName.split(".")[0].lower()

return functionName

@staticmethod
def getFileDialogDirectory() -> str:
"""
Return starting directory for ``QFileDialog`` file open/save
:return: starting directory for ``QFileDialog`` file open/save
:rtype: ``str``
"""
return ""

@staticmethod
def getFileDialogFilter() -> str:
"""
Return ``str`` standard file open/save filter for ``QFileDialog``
def _indent_code(string: str):
lines = string.split("\n")
indentedLines = ["\n " + line for line in lines]
indentedCode = "".join(indentedLines)
return indentedCode
:return: standard file open/save filter for ``QFileDialog``
:rtype: ``str``
"""
return "Python files (*.py);;All files (*)"
7 changes: 7 additions & 0 deletions nodedge/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,10 @@ def widgetsAt(pos):
widget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, False)

return widgets


def indentCode(string: str):
lines = string.split("\n")
indentedLines = ["\n " + line for line in lines]
indentedCode = "".join(indentedLines)
return indentedCode
27 changes: 23 additions & 4 deletions tests/scene_coder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from nodedge.blocks.custom.output_block import OutputBlock
from nodedge.edge import Edge
from nodedge.editor_widget import EditorWidget
from nodedge.utils import indentCode


@pytest.fixture
Expand Down Expand Up @@ -45,12 +46,30 @@ def filledScene(emptyScene):

def test_generateCode(filledScene):
expectedResult = (
"var_0 = 2.0\n"
+ "var_1 = 1.0\n"
+ "var_2 = add(var_1, var_0)\n"
+ "return [var_2]"
"var_0 = 2.0\n"
+ "var_1 = 1.0\n"
+ "var_2 = add(var_1, var_0)\n"
+ "return [var_2]"
)

_, generatedCode = filledScene.coder.generateCode()

assert generatedCode == expectedResult


def test_addImports(filledScene):
orderedNodeList, generatedCode = filledScene.coder.generateCode()

filename = "unnamed"
imports = "from operator import add\n\n\n"

expectedResult = (
imports
+ f"def {filename}():"
+ indentCode(generatedCode)
+ f"\n\n\nif __name__ == '__main__':\n {filename}()\n"
)

generatedFileString = filledScene.coder.addImports(orderedNodeList, generatedCode)

assert generatedFileString == expectedResult

0 comments on commit 5658269

Please sign in to comment.