File indexing completed on 2025-03-09 04:10:20
0001 """ 0002 SPDX-FileCopyrightText: 2017 Eliakin Costa <eliakim170@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 """ 0006 from PyQt5.QtWidgets import QAction 0007 from PyQt5.QtGui import QIcon, QKeySequence 0008 from PyQt5.QtCore import Qt 0009 import sys 0010 import traceback 0011 import inspect 0012 from . import docwrapper 0013 from .... import utils 0014 import krita 0015 0016 if sys.version_info[0] > 2: 0017 import importlib 0018 from importlib.machinery import SourceFileLoader 0019 else: 0020 import imp 0021 0022 PYTHON27 = sys.version_info.major == 2 and sys.version_info.minor == 7 0023 PYTHON33 = sys.version_info.major == 3 and sys.version_info.minor == 3 0024 PYTHON34 = sys.version_info.major == 3 and sys.version_info.minor == 4 0025 EXEC_NAMESPACE = "__main__" # namespace that user scripts will run in 0026 0027 0028 class RunAction(QAction): 0029 0030 def __init__(self, scripter, parent=None): 0031 super(RunAction, self).__init__(parent) 0032 self.scripter = scripter 0033 0034 self.editor = self.scripter.uicontroller.editor 0035 self.output = self.scripter.uicontroller.findTabWidget(i18n('Output'), 'OutPutTextEdit') 0036 0037 self.triggered.connect(self.run) 0038 0039 self.setText(i18n("Run")) 0040 self.setToolTip(i18n('Run Ctrl+R')) 0041 self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) 0042 self.setIcon(utils.getThemedIcon(':/icons/run.svg')) 0043 0044 @property 0045 def parent(self): 0046 return 'toolBar', 0047 0048 def run(self): 0049 """ This method execute python code from an activeDocument (file) or direct 0050 from editor (ui_scripter/editor/pythoneditor.py). When executing code 0051 from a file, we use importlib to load this module/file and with 0052 "users_script" name. That's implementation seeks for a "main()" function in the script. 0053 When executing code from editor without creating a file, we compile 0054 this script to bytecode and we execute this in an empty scope. That's 0055 faster than use exec directly and cleaner, because we are using an empty scope. """ 0056 0057 self.scripter.uicontroller.setActiveWidget(i18n('Output')) 0058 stdout = sys.stdout 0059 stderr = sys.stderr 0060 output = docwrapper.DocWrapper(self.output.document()) 0061 if (self.editor._documentModified is True): 0062 output.write("==== Warning: Script not saved! ====\n") 0063 else: 0064 output.write("======================================\n") 0065 sys.stdout = output 0066 sys.stderr = output 0067 0068 script = self.editor.document().toPlainText() 0069 document = self.scripter.documentcontroller.activeDocument 0070 0071 try: 0072 if document and self.editor._documentModified is False: 0073 if PYTHON27: 0074 users_module = self.run_py2_document(document) 0075 else: 0076 users_module = self.run_py3_document(document) 0077 0078 # maybe script is to be execed, maybe main needs to be invoked 0079 # if there is a main() then execute it, otherwise don't worry... 0080 if hasattr(users_module, "main") and inspect.isfunction(users_module.main): 0081 users_module.main() 0082 else: 0083 code = compile(script, '<string>', 'exec') 0084 globals_dict = {"__name__": EXEC_NAMESPACE} 0085 exec(code, globals_dict) 0086 0087 except SystemExit: 0088 # user typed quit() or exit() 0089 self.scripter.uicontroller.closeScripter() 0090 except Exception: 0091 # Provide context (line number and text) for an error that is caught. 0092 # Ordinarily, syntax and Indent errors are caught during initial 0093 # compilation in exec(), and the traceback traces back to this file. 0094 # So these need to be treated separately. 0095 # Other errors trace back to the file/script being run. 0096 type_, value_, traceback_ = sys.exc_info() 0097 if type_ == SyntaxError: 0098 errorMessage = "%s\n%s" % (value_.text.rstrip(), " " * (value_.offset - 1) + "^") 0099 # rstrip to remove trailing \n, output needs to be fixed width font for the ^ to align correctly 0100 errorText = "Syntax Error on line %s" % value_.lineno 0101 elif type_ == IndentationError: 0102 # (no offset is provided for an IndentationError 0103 errorMessage = value_.text.rstrip() 0104 errorText = "Unexpected Indent on line %s" % value_.lineno 0105 else: 0106 errorText = traceback.format_exception_only(type_, value_)[0] 0107 format_string = "In file: {0}\nIn function: {2} at line: {1}. Line with error:\n{3}" 0108 tbList = traceback.extract_tb(traceback_) 0109 tb = tbList[-1] 0110 errorMessage = format_string.format(*tb) 0111 m = "\n**********************\n%s\n%s\n**********************\n" % (errorText, errorMessage) 0112 output.write(m) 0113 0114 sys.stdout = stdout 0115 sys.stderr = stderr 0116 0117 # scroll to bottom of output 0118 bottom = self.output.verticalScrollBar().maximum() 0119 self.output.verticalScrollBar().setValue(bottom) 0120 0121 def run_py2_document(self, document): 0122 """ Loads and executes an external script using Python 2 specific operations 0123 and returns the loaded module for further execution if needed. 0124 """ 0125 try: 0126 user_module = imp.load_source(EXEC_NAMESPACE, document.filePath) 0127 except Exception as e: 0128 raise e 0129 0130 return user_module 0131 0132 def run_py3_document(self, document): 0133 """ Loads and executes an external script using Python 3 specific operations 0134 and returns the loaded module for further execution if needed. 0135 """ 0136 spec = importlib.util.spec_from_file_location(EXEC_NAMESPACE, document.filePath) 0137 try: 0138 users_module = importlib.util.module_from_spec(spec) 0139 spec.loader.exec_module(users_module) 0140 0141 except AttributeError as e: # no module from spec 0142 if PYTHON34 or PYTHON33: 0143 loader = SourceFileLoader(EXEC_NAMESPACE, document.filePath) 0144 users_module = loader.load_module() 0145 else: 0146 raise e 0147 0148 return users_module