Krita Source Code Documentation
Loading...
Searching...
No Matches
runaction.py
Go to the documentation of this file.
1"""
2SPDX-FileCopyrightText: 2017 Eliakin Costa <eliakim170@gmail.com>
3
4SPDX-License-Identifier: GPL-2.0-or-later
5"""
6try:
7 from PyQt6.QtGui import QKeySequence, QAction
8 from PyQt6.QtCore import Qt
9except:
10 from PyQt5.QtWidgets import QAction
11 from PyQt5.QtGui import QKeySequence
12 from PyQt5.QtCore import Qt
13import sys
14import traceback
15import inspect
16from . import docwrapper
17from .... import utils
18from builtins import i18n
19
20import importlib
21from importlib.machinery import SourceFileLoader
22
23PYTHON33 = sys.version_info.major == 3 and sys.version_info.minor == 3
24PYTHON34 = sys.version_info.major == 3 and sys.version_info.minor == 4
25EXEC_NAMESPACE = "__main__" # namespace that user scripts will run in
26
27
28class RunAction(QAction):
29
30 def __init__(self, scripter, parent=None):
31 super(RunAction, self).__init__(parent)
32 self.scripter = scripter
33
34 self.editor = self.scripter.uicontroller.editor
35 self.output = self.scripter.uicontroller.findTabWidget(i18n('Output'), 'OutPutTextEdit')
36
37 self.triggered.connect(self.runrun)
38
39 self.setText(i18n("Run"))
40 self.setToolTip(i18n('Run Ctrl+R'))
41 self.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_R))
42 self.setIcon(utils.getThemedIcon(':/icons/run.svg'))
43
44 @property
45 def parent(self):
46 return 'toolBar',
47
48 def run(self):
49 """ This method execute python code from an activeDocument (file) or direct
50 from editor (ui_scripter/editor/pythoneditor.py). When executing code
51 from a file, we use importlib to load this module/file and with
52 "users_script" name. That's implementation seeks for a "main()" function in the script.
53 When executing code from editor without creating a file, we compile
54 this script to bytecode and we execute this in an empty scope. That's
55 faster than use exec directly and cleaner, because we are using an empty scope. """
56
57 self.scripter.uicontroller.setActiveWidget(i18n('Output'))
58 stdout = sys.stdout
59 stderr = sys.stderr
60 output = docwrapper.DocWrapper(self.output.document())
61 if (self.editor._documentModified is True):
62 output.write("==== Warning: Script not saved! ====\n")
63 else:
64 output.write("======================================\n")
65 sys.stdout = output
66 sys.stderr = output
67
68 script = self.editor.document().toPlainText()
69 document = self.scripter.documentcontroller.activeDocument
70
71 try:
72 if document and self.editor._documentModified is False:
73 users_module = self.run_py3_document(document)
74
75 # maybe script is to be execed, maybe main needs to be invoked
76 # if there is a main() then execute it, otherwise don't worry...
77 if hasattr(users_module, "main") and inspect.isfunction(users_module.main):
78 users_module.main()
79 else:
80 code = compile(script, '<string>', 'exec')
81 globals_dict = {"__name__": EXEC_NAMESPACE}
82 exec(code, globals_dict)
83
84 except SystemExit:
85 # user typed quit() or exit()
86 self.scripter.uicontroller.closeScripter()
87 except Exception:
88 # Provide context (line number and text) for an error that is caught.
89 # Ordinarily, syntax and Indent errors are caught during initial
90 # compilation in exec(), and the traceback traces back to this file.
91 # So these need to be treated separately.
92 # Other errors trace back to the file/script being run.
93 type_, value_, traceback_ = sys.exc_info()
94 if type_ == SyntaxError:
95 errorMessage = "%s\n%s" % (value_.text.rstrip(), " " * (value_.offset - 1) + "^")
96 # rstrip to remove trailing \n, output needs to be fixed width font for the ^ to align correctly
97 errorText = "Syntax Error on line %s" % value_.lineno
98 elif type_ == IndentationError:
99 # (no offset is provided for an IndentationError
100 errorMessage = value_.text.rstrip()
101 errorText = "Unexpected Indent on line %s" % value_.lineno
102 else:
103 errorText = traceback.format_exception_only(type_, value_)[0]
104 format_string = "In file: {0}\nIn function: {2} at line: {1}. Line with error:\n{3}"
105 tbList = traceback.extract_tb(traceback_)
106 tb = tbList[-1]
107 errorMessage = format_string.format(*tb)
108 m = "\n**********************\n%s\n%s\n**********************\n" % (errorText, errorMessage)
109 output.write(m)
110
111 sys.stdout = stdout
112 sys.stderr = stderr
113
114 # scroll to bottom of output
115 bottom = self.output.verticalScrollBar().maximum()
116 self.output.verticalScrollBar().setValue(bottom)
117
118 def run_py3_document(self, document):
119 """ Loads and executes an external script using Python 3 specific operations
120 and returns the loaded module for further execution if needed.
121 """
122 spec = importlib.util.spec_from_file_location(EXEC_NAMESPACE, document.filePath)
123 try:
124 users_module = importlib.util.module_from_spec(spec)
125 spec.loader.exec_module(users_module)
126
127 except AttributeError as e: # no module from spec
128 if PYTHON34 or PYTHON33:
129 loader = SourceFileLoader(EXEC_NAMESPACE, document.filePath)
130 users_module = loader.load_module()
131 else:
132 raise e
133
134 return users_module
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))