PrEditor 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- preditor/__init__.py +315 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +165 -0
- preditor/cli.py +192 -0
- preditor/config.py +318 -0
- preditor/constants.py +13 -0
- preditor/contexts.py +210 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/.hab.json +10 -0
- preditor/dccs/maya/PrEditor_maya.mod +1 -0
- preditor/dccs/maya/README.md +22 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
- preditor/dccs/studiomax/PackageContents.xml +32 -0
- preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
- preditor/dccs/studiomax/README.md +17 -0
- preditor/dccs/studiomax/preditor.ms +16 -0
- preditor/dccs/studiomax/preditor_menu.mnx +7 -0
- preditor/debug.py +149 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +165 -0
- preditor/gui/__init__.py +56 -0
- preditor/gui/app.py +163 -0
- preditor/gui/codehighlighter.py +289 -0
- preditor/gui/completer.py +237 -0
- preditor/gui/console.py +605 -0
- preditor/gui/console_base.py +911 -0
- preditor/gui/dialog.py +181 -0
- preditor/gui/drag_tab_bar.py +625 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +69 -0
- preditor/gui/find_files.py +137 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
- preditor/gui/group_tab_widget/__init__.py +0 -0
- preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
- preditor/gui/level_buttons.py +358 -0
- preditor/gui/logger_window_handler.py +77 -0
- preditor/gui/logger_window_plugin.py +35 -0
- preditor/gui/loggerwindow.py +2405 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/output_console.py +11 -0
- preditor/gui/qtdesigner/__init__.py +21 -0
- preditor/gui/qtdesigner/_log_plugin.py +29 -0
- preditor/gui/qtdesigner/console_base_plugin.py +48 -0
- preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
- preditor/gui/set_text_editor_path_dialog.py +61 -0
- preditor/gui/status_label.py +99 -0
- preditor/gui/suggest_path_quotes_dialog.py +50 -0
- preditor/gui/ui/editor_chooser.ui +93 -0
- preditor/gui/ui/errordialog.ui +74 -0
- preditor/gui/ui/find_files.ui +140 -0
- preditor/gui/ui/loggerwindow.ui +1909 -0
- preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
- preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
- preditor/gui/window.py +161 -0
- preditor/gui/workbox_mixin.py +1139 -0
- preditor/gui/workbox_text_edit.py +136 -0
- preditor/gui/workboxwidget.py +315 -0
- preditor/logging_config.py +55 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +381 -0
- preditor/resource/environment_variables.html +26 -0
- preditor/resource/error_mail.html +85 -0
- preditor/resource/error_mail_inline.html +41 -0
- preditor/resource/img/README.md +17 -0
- preditor/resource/img/arrow_forward.png +0 -0
- preditor/resource/img/check-bold.png +0 -0
- preditor/resource/img/chevron-down.png +0 -0
- preditor/resource/img/chevron-up.png +0 -0
- preditor/resource/img/close-thick.png +0 -0
- preditor/resource/img/comment-edit.png +0 -0
- preditor/resource/img/content-copy.png +0 -0
- preditor/resource/img/content-cut.png +0 -0
- preditor/resource/img/content-duplicate.png +0 -0
- preditor/resource/img/content-paste.png +0 -0
- preditor/resource/img/content-save.png +0 -0
- preditor/resource/img/debug_disabled.png +0 -0
- preditor/resource/img/eye-check.png +0 -0
- preditor/resource/img/file-plus.png +0 -0
- preditor/resource/img/file-remove.png +0 -0
- preditor/resource/img/format-align-left.png +0 -0
- preditor/resource/img/format-letter-case-lower.png +0 -0
- preditor/resource/img/format-letter-case-upper.png +0 -0
- preditor/resource/img/format-letter-case.svg +1 -0
- preditor/resource/img/information.png +0 -0
- preditor/resource/img/logging_critical.png +0 -0
- preditor/resource/img/logging_custom.png +0 -0
- preditor/resource/img/logging_debug.png +0 -0
- preditor/resource/img/logging_error.png +0 -0
- preditor/resource/img/logging_info.png +0 -0
- preditor/resource/img/logging_not_set.png +0 -0
- preditor/resource/img/logging_warning.png +0 -0
- preditor/resource/img/marker.png +0 -0
- preditor/resource/img/play.png +0 -0
- preditor/resource/img/playlist-play.png +0 -0
- preditor/resource/img/plus-minus-variant.png +0 -0
- preditor/resource/img/preditor.ico +0 -0
- preditor/resource/img/preditor.png +0 -0
- preditor/resource/img/preditor.psd +0 -0
- preditor/resource/img/preditor.svg +44 -0
- preditor/resource/img/regex.svg +1 -0
- preditor/resource/img/restart.svg +1 -0
- preditor/resource/img/skip-forward-outline.png +0 -0
- preditor/resource/img/skip-next-outline.png +0 -0
- preditor/resource/img/skip-next.png +0 -0
- preditor/resource/img/skip-previous.png +0 -0
- preditor/resource/img/subdirectory-arrow-right.png +0 -0
- preditor/resource/img/text-search-variant.png +0 -0
- preditor/resource/img/warning-big.png +0 -0
- preditor/resource/lang/python.json +30 -0
- preditor/resource/pref_updates/pref_updates.json +17 -0
- preditor/resource/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +76 -0
- preditor/resource/stylesheet/Dark.css +210 -0
- preditor/scintilla/__init__.py +40 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +97 -0
- preditor/scintilla/delayables/spell_check.py +174 -0
- preditor/scintilla/documenteditor.py +1924 -0
- preditor/scintilla/finddialog.py +68 -0
- preditor/scintilla/lang/__init__.py +80 -0
- preditor/scintilla/lang/config/bash.ini +15 -0
- preditor/scintilla/lang/config/batch.ini +14 -0
- preditor/scintilla/lang/config/cpp.ini +19 -0
- preditor/scintilla/lang/config/css.ini +19 -0
- preditor/scintilla/lang/config/eyeonscript.ini +17 -0
- preditor/scintilla/lang/config/html.ini +21 -0
- preditor/scintilla/lang/config/javascript.ini +24 -0
- preditor/scintilla/lang/config/lua.ini +16 -0
- preditor/scintilla/lang/config/maxscript.ini +20 -0
- preditor/scintilla/lang/config/mel.ini +18 -0
- preditor/scintilla/lang/config/mu.ini +22 -0
- preditor/scintilla/lang/config/nsi.ini +19 -0
- preditor/scintilla/lang/config/perl.ini +19 -0
- preditor/scintilla/lang/config/puppet.ini +19 -0
- preditor/scintilla/lang/config/python.ini +28 -0
- preditor/scintilla/lang/config/ruby.ini +19 -0
- preditor/scintilla/lang/config/sql.ini +7 -0
- preditor/scintilla/lang/config/xml.ini +21 -0
- preditor/scintilla/lang/config/yaml.ini +18 -0
- preditor/scintilla/lang/language.py +240 -0
- preditor/scintilla/lexers/__init__.py +0 -0
- preditor/scintilla/lexers/cpplexer.py +22 -0
- preditor/scintilla/lexers/javascriptlexer.py +27 -0
- preditor/scintilla/lexers/maxscriptlexer.py +235 -0
- preditor/scintilla/lexers/mellexer.py +369 -0
- preditor/scintilla/lexers/mulexer.py +33 -0
- preditor/scintilla/lexers/pythonlexer.py +42 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +72 -0
- preditor/stream/console_handler.py +169 -0
- preditor/stream/director.py +144 -0
- preditor/stream/manager.py +97 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +191 -0
- preditor/utils/call_stack.py +86 -0
- preditor/utils/cute.py +106 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +338 -0
- preditor/version.py +34 -0
- preditor/weakref.py +363 -0
- preditor-2.1.0.dist-info/METADATA +308 -0
- preditor-2.1.0.dist-info/RECORD +179 -0
- preditor-2.1.0.dist-info/WHEEL +5 -0
- preditor-2.1.0.dist-info/entry_points.txt +19 -0
- preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
- preditor-2.1.0.dist-info/top_level.txt +3 -0
- tests/encodings/test_ecoding.py +33 -0
- tests/find_files/test_find_files.py +74 -0
- tests/ide/test_delayable_engine.py +171 -0
preditor/config.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_if_unlocked(track_state=False):
|
|
10
|
+
def _set_if_unlocked(func):
|
|
11
|
+
"""Decorate property setter functions to prevent editing after locking."""
|
|
12
|
+
|
|
13
|
+
@wraps(func)
|
|
14
|
+
def wrapper(self, value):
|
|
15
|
+
if not self.is_locked(func.__name__, track_state=track_state):
|
|
16
|
+
# logger.debug(f'"{func.__name__}" is unlocked and can be set: {value}')
|
|
17
|
+
return func(self, value)
|
|
18
|
+
# logger.info(
|
|
19
|
+
# f'"{func.__name__}" is locked and can no-longer be set: {value}'
|
|
20
|
+
# )
|
|
21
|
+
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
return _set_if_unlocked
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PreditorConfig:
|
|
28
|
+
"""Global configuration of PrEditor's instance.
|
|
29
|
+
|
|
30
|
+
When creating the main PrEditor instance it will use `preditor.config` to
|
|
31
|
+
control how its initialized. That stores an instance of this class.
|
|
32
|
+
|
|
33
|
+
Once `preditor.instance(create=True)` the properties on this class will then
|
|
34
|
+
silently ignore any attempts to set most of its properties. Where noted in
|
|
35
|
+
the property doc-strings, some properties are used to install a setting that
|
|
36
|
+
can't be automatically undone, so they will stop responding after the first
|
|
37
|
+
setting.
|
|
38
|
+
|
|
39
|
+
While this class does not force a singleton pattern, it is expected in normal
|
|
40
|
+
operation that there will only ever be a single instance made and it is stored
|
|
41
|
+
on `preditor.config` when first imported. Things like excepthook and streams
|
|
42
|
+
modify python's state which could lead to duplicate outputs, multiple error
|
|
43
|
+
prompts or other undesirable behavior.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self._locks = {}
|
|
48
|
+
self._name = None
|
|
49
|
+
self._logging = False
|
|
50
|
+
self._headless_callback = None
|
|
51
|
+
self._on_create_callback = None
|
|
52
|
+
self._parent_callback = None
|
|
53
|
+
self._error_dialog_class = True
|
|
54
|
+
self._excepthooks = []
|
|
55
|
+
|
|
56
|
+
def dump(self, indent=0):
|
|
57
|
+
"""A convenient way to inspect the current configuration."""
|
|
58
|
+
ret = [
|
|
59
|
+
f"{' ' * indent}name: {self.name}",
|
|
60
|
+
f"{' ' * indent}streams: {self.streams}",
|
|
61
|
+
f"{' ' * indent}excepthook: {self.excepthook!r}",
|
|
62
|
+
f"{' ' * indent}excepthooks: {self.excepthooks!r}",
|
|
63
|
+
f"{' ' * indent}error_dialog_class: {self.error_dialog_class!r}",
|
|
64
|
+
f"{' ' * indent}logging: {self.logging}",
|
|
65
|
+
f"{' ' * indent}headless_callback: {self.headless_callback!r}",
|
|
66
|
+
f"{' ' * indent}parent_callback: {self.parent_callback!r}",
|
|
67
|
+
f"{' ' * indent}on_create_callback: {self.on_create_callback!r}",
|
|
68
|
+
]
|
|
69
|
+
return '\n'.join(ret)
|
|
70
|
+
|
|
71
|
+
def is_locked(self, value, track_state=False):
|
|
72
|
+
"""Returns if value is locked and can not be updated.
|
|
73
|
+
|
|
74
|
+
Once `preditor.instance` returns a value this will always return False.
|
|
75
|
+
The config settings should not be modified once the instance is created.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
value (str): The name of the value to check. Should match a property
|
|
79
|
+
name on this class.
|
|
80
|
+
track_state (bool, optional): If True then also check if value has
|
|
81
|
+
been flagged as locked. This indicates that it was already set
|
|
82
|
+
by a previous call and can no longer be modified even if instance
|
|
83
|
+
is False.
|
|
84
|
+
"""
|
|
85
|
+
if self.instance(create=False):
|
|
86
|
+
# if the instance is created all items are locked
|
|
87
|
+
return True
|
|
88
|
+
if track_state:
|
|
89
|
+
return self._locks.get(value, False)
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def error_dialog_class(self):
|
|
94
|
+
"""Dialog class shown if PrEditor isn't visible and a error happens.
|
|
95
|
+
|
|
96
|
+
This dialog is used to prompt the user to show PrEditor and can be
|
|
97
|
+
sub-classed to add extra functionality. This is called by
|
|
98
|
+
`PreditorExceptHook.ask_to_show_logger` method when added to
|
|
99
|
+
`preditor.config.excepthooks`.
|
|
100
|
+
|
|
101
|
+
If set to `True` then `blurdev.gui.errordialog.ErrorDialog` is used. When
|
|
102
|
+
replacing this, it should be a sub-class of that class or re-implement
|
|
103
|
+
its api.
|
|
104
|
+
|
|
105
|
+
You can use an EntryPoint string like `preditor.gui.errordialog:ErrorDialog`
|
|
106
|
+
instead of passing the actual class object. This lets you delay the import
|
|
107
|
+
until actually needed.
|
|
108
|
+
"""
|
|
109
|
+
return self._error_dialog_class
|
|
110
|
+
|
|
111
|
+
@error_dialog_class.setter
|
|
112
|
+
def error_dialog_class(self, cls):
|
|
113
|
+
self._error_dialog_class = cls
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def excepthook(self):
|
|
117
|
+
"""Installs a `sys.excepthook` handler when first set to True.
|
|
118
|
+
|
|
119
|
+
Replaces `sys.excepthook` with a interactive exception handler that
|
|
120
|
+
prompts the user to show PrEditor when an python exception is raised.
|
|
121
|
+
It is recommended that you only add the excepthook once the Qt UI is
|
|
122
|
+
initialized. See: :py:class:`preditor.excepthooks.PreditorExceptHook`.
|
|
123
|
+
"""
|
|
124
|
+
return self._locks.get("excepthook", False)
|
|
125
|
+
|
|
126
|
+
@excepthook.setter
|
|
127
|
+
@set_if_unlocked(track_state=True)
|
|
128
|
+
def excepthook(self, value):
|
|
129
|
+
if not value:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Install the excepthook:
|
|
133
|
+
import preditor.excepthooks
|
|
134
|
+
|
|
135
|
+
# Note: install checks if the current excepthook is a instance of this
|
|
136
|
+
# class and prevents installing a second time.
|
|
137
|
+
preditor.excepthooks.PreditorExceptHook.install()
|
|
138
|
+
|
|
139
|
+
# Disable future setting via `set_if_unlocked`, the `PreditorExceptHook`
|
|
140
|
+
# is a chaining except hook that calls the previous excepthook when called.
|
|
141
|
+
# We don't want to install multiple automatically, the user can define that
|
|
142
|
+
# logic if required.
|
|
143
|
+
self._locks["excepthook"] = True
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def excepthooks(self):
|
|
147
|
+
"""A list of callables that are called when an exception is handled.
|
|
148
|
+
|
|
149
|
+
If `excepthook` is enabled installing `PreditorExceptHook` then it will
|
|
150
|
+
call each item in this list. The signature of the function should be
|
|
151
|
+
`callable(*args)`. If not configured it will automatically install default
|
|
152
|
+
callables. You can add `None` to disable this.
|
|
153
|
+
"""
|
|
154
|
+
return self._excepthooks
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def headless_callback(self):
|
|
158
|
+
"""A pointer to a method that is called by `is_headless`.
|
|
159
|
+
|
|
160
|
+
This callback returns a bool indicating if PrEditor should attempt to
|
|
161
|
+
create GUI elements. Application integrations can set this callback to
|
|
162
|
+
give them control over what PrEditor gets parent to.
|
|
163
|
+
"""
|
|
164
|
+
return self._headless_callback
|
|
165
|
+
|
|
166
|
+
@headless_callback.setter
|
|
167
|
+
@set_if_unlocked()
|
|
168
|
+
def headless_callback(self, cb):
|
|
169
|
+
self._headless_callback = cb
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def instance(cls, parent=None, run_workbox=False, create=True):
|
|
173
|
+
"""Returns the existing instance of the PrEditor gui creating it on first call.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
parent (QWidget, optional): If the instance hasn't been created yet, create
|
|
177
|
+
it and parent it to this object.
|
|
178
|
+
run_workbox (bool, optional): If the instance hasn't been created yet, this
|
|
179
|
+
will execute the active workbox's code once fully initialized.
|
|
180
|
+
create (bool, optional): Returns None if the instance has not been created.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Returns a fully initialized instance of the PrEditor gui. If called more
|
|
184
|
+
than once, the same instance will be returned. If create is False, it may
|
|
185
|
+
return None.
|
|
186
|
+
"""
|
|
187
|
+
from .gui.loggerwindow import LoggerWindow
|
|
188
|
+
|
|
189
|
+
return LoggerWindow.instance(
|
|
190
|
+
parent=parent, run_workbox=run_workbox, create=create
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def is_headless(self):
|
|
194
|
+
"""Returns True if PrEditor should not create GUI items.
|
|
195
|
+
|
|
196
|
+
Returns None if the `headless_callback` has not been configured. Otherwise
|
|
197
|
+
returns the result of calling `headless_callback()` which should return a bool.
|
|
198
|
+
"""
|
|
199
|
+
if not self.headless_callback:
|
|
200
|
+
return None
|
|
201
|
+
return self.headless_callback()
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def logging(self):
|
|
205
|
+
"""Restore the python logging configuration settings that were recorded
|
|
206
|
+
the last time PrEditor prefs were saved.
|
|
207
|
+
|
|
208
|
+
If called multiple times with different name's before the instance is
|
|
209
|
+
created, this will reset the logging configuration from the previous
|
|
210
|
+
name if logging prefs exist.
|
|
211
|
+
"""
|
|
212
|
+
return self._logging
|
|
213
|
+
|
|
214
|
+
@logging.setter
|
|
215
|
+
def logging(self, state):
|
|
216
|
+
self._logging = state
|
|
217
|
+
self.update_logging()
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def name(self):
|
|
221
|
+
"""The name to use for the global instance of PrEditor.
|
|
222
|
+
|
|
223
|
+
Once this has been set, you can call `preditor.launch` without passing
|
|
224
|
+
name to access the main instance. The name controls what preferences
|
|
225
|
+
are loaded and used by PrEditor including the workbox tabs."""
|
|
226
|
+
return self._name
|
|
227
|
+
|
|
228
|
+
@name.setter
|
|
229
|
+
@set_if_unlocked()
|
|
230
|
+
def name(self, name):
|
|
231
|
+
changed = self.name != name
|
|
232
|
+
self._name = name
|
|
233
|
+
|
|
234
|
+
if changed:
|
|
235
|
+
# If the core name was changed attempt to update the logging config.
|
|
236
|
+
self.update_logging()
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def on_create_callback(self):
|
|
240
|
+
"""A pointer to a method that is called on LoggerWindow instance create.
|
|
241
|
+
|
|
242
|
+
This callback accepts the instance and can be used to customize the
|
|
243
|
+
LoggerWindow instance when it is first created.
|
|
244
|
+
"""
|
|
245
|
+
return self._on_create_callback
|
|
246
|
+
|
|
247
|
+
@on_create_callback.setter
|
|
248
|
+
@set_if_unlocked()
|
|
249
|
+
def on_create_callback(self, cb):
|
|
250
|
+
self._on_create_callback = cb
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def parent_callback(self):
|
|
254
|
+
"""A pointer to a method that is called by `self.root_window`.
|
|
255
|
+
|
|
256
|
+
This callback returns a QWidget to use as the parent of the LoggerWindow
|
|
257
|
+
when the instance is first created. This can be used by DCC's to set the
|
|
258
|
+
parent to their main window."""
|
|
259
|
+
return self._parent_callback
|
|
260
|
+
|
|
261
|
+
@parent_callback.setter
|
|
262
|
+
@set_if_unlocked()
|
|
263
|
+
def parent_callback(self, cb):
|
|
264
|
+
self._parent_callback = cb
|
|
265
|
+
|
|
266
|
+
def root_window(self):
|
|
267
|
+
"""The QWidget to use as the parent of PrEditor widgets."""
|
|
268
|
+
# If a parent widget callback was configured, use it
|
|
269
|
+
if self.parent_callback is not None:
|
|
270
|
+
return self.parent_callback()
|
|
271
|
+
|
|
272
|
+
# Otherwise, attempt to find the top level widget from Qt
|
|
273
|
+
from .gui.app import App
|
|
274
|
+
|
|
275
|
+
return App.root_window()
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def streams(self):
|
|
279
|
+
"""Installs the stream managers when first set to True.
|
|
280
|
+
|
|
281
|
+
Install the stream manager to capture any stdout/stderr text written.
|
|
282
|
+
Later when calling launch, the LoggerWindow will show all of the captured
|
|
283
|
+
text. This lets you only create the LoggerWindow IF you need to show it,
|
|
284
|
+
but when you do it will have all of the std stream text written after
|
|
285
|
+
this is enabled."""
|
|
286
|
+
return self._locks.get("streams", False)
|
|
287
|
+
|
|
288
|
+
@streams.setter
|
|
289
|
+
@set_if_unlocked(track_state=True)
|
|
290
|
+
def streams(self, value):
|
|
291
|
+
if not value:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
# Install the stream manager to capture output
|
|
295
|
+
from .stream import install_to_std
|
|
296
|
+
|
|
297
|
+
install_to_std()
|
|
298
|
+
|
|
299
|
+
# Disable re-installing streams.
|
|
300
|
+
self._locks["streams"] = True
|
|
301
|
+
|
|
302
|
+
def update_logging(self):
|
|
303
|
+
"""Install/replace the logging config.
|
|
304
|
+
|
|
305
|
+
This does nothing unless `logging` is set to True and `name` is set.
|
|
306
|
+
|
|
307
|
+
Note: When called repeatedly, this won't remove old logger's added by
|
|
308
|
+
previous calls so you may see some loggers added that were never
|
|
309
|
+
actually added by code other than the LoggingConfig.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
if not (self.logging and self.name):
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
from .logging_config import LoggingConfig
|
|
316
|
+
|
|
317
|
+
cfg = LoggingConfig(core_name=self.name)
|
|
318
|
+
cfg.load()
|
preditor/constants.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StreamType(enum.Flag):
|
|
5
|
+
"""Different types of streams used by PrEditor."""
|
|
6
|
+
|
|
7
|
+
STDERR = enum.auto()
|
|
8
|
+
STDIN = enum.auto()
|
|
9
|
+
STDOUT = enum.auto()
|
|
10
|
+
CONSOLE = enum.auto()
|
|
11
|
+
"""Write directly to the console ignoring STDERR/STDOUT filters."""
|
|
12
|
+
RESULT = enum.auto()
|
|
13
|
+
"""Write directly to ConsolePrEdit's result output without using stdout/err."""
|
preditor/contexts.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import List, Tuple
|
|
6
|
+
|
|
7
|
+
_LOGGER = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ErrorReport(object):
|
|
11
|
+
"""Allows you to provide additional debug info if a error happens in this context.
|
|
12
|
+
|
|
13
|
+
PrEditor can send a error email when any python error is raised.
|
|
14
|
+
Sometimes just a traceback does not provide enough information to debug the
|
|
15
|
+
traceback. This class allows you to provide additional information to the error
|
|
16
|
+
report only if it is generated. For example if your treegrunt environment does not
|
|
17
|
+
have a email setup, or the current debug level is not set to Disabled.
|
|
18
|
+
|
|
19
|
+
ErrorReport can be used as a with context, or as a function decorator.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
This example shows a class using both the with context and a decorated method.
|
|
23
|
+
|
|
24
|
+
from preditor.contexts import ErrorReport
|
|
25
|
+
class Test(object):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.value = None
|
|
28
|
+
def errorInfo(self):
|
|
29
|
+
# The text returned by this function will be included in the error email
|
|
30
|
+
return 'Info about the Test class: {}'.format(self.value)
|
|
31
|
+
def doStuff(self):
|
|
32
|
+
with ErrorReport(self.errorInfo, 'Test.doStuff'):
|
|
33
|
+
self.value = 'doStuff'
|
|
34
|
+
raise RuntimeError("BILL")
|
|
35
|
+
@ErrorReport(errorInfo, 'Test.doMoreStuff')
|
|
36
|
+
def doMoreStuff(self):
|
|
37
|
+
self.value = 'doMoreStuff'
|
|
38
|
+
raise RuntimeError("BOB")
|
|
39
|
+
|
|
40
|
+
Using this class does not initialize the Python Logger, so you don't need to worry
|
|
41
|
+
if your class is running headless and not use this class. However unless you set up
|
|
42
|
+
your own error reporting system the callbacks will not be called and nothing will be
|
|
43
|
+
reported.
|
|
44
|
+
|
|
45
|
+
If you want to set up your own error reporting system you need to set
|
|
46
|
+
`ErrorReport.enabled = True`. Then you will need to call ErrorReport.clearReports()
|
|
47
|
+
any time excepthook is called. This prevents a buildup of all error reports any time
|
|
48
|
+
a exception occurs. It should always be in place when you set enabled == True to
|
|
49
|
+
prevent wasting memory. Calling ErrorReport.generateReport() will return the info
|
|
50
|
+
you should include in your report. Calling generateReport is optional, but must be
|
|
51
|
+
called before calling clearReports.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
callback (function): If a exception happens this function is called and its
|
|
55
|
+
returned value is added to the error email if sent. No arguments are passed
|
|
56
|
+
to this function and it is expected to only return a string.
|
|
57
|
+
title (str, optional): This short string is added to the title of the
|
|
58
|
+
ErrorReport.
|
|
59
|
+
Attributes:
|
|
60
|
+
enabled (bool): If False(the default), then all callbacks are cleared even if
|
|
61
|
+
there is a exception. This is used to prevent these functions from leaking
|
|
62
|
+
memory if there isn't a excepthook calling clearReports.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
__reports__: List[Tuple[str, str]] = []
|
|
66
|
+
enabled = False
|
|
67
|
+
|
|
68
|
+
def __init__(self, callback, title=''):
|
|
69
|
+
self._callback = callback
|
|
70
|
+
self._title = title
|
|
71
|
+
|
|
72
|
+
def __call__(self, funct):
|
|
73
|
+
def wrapper(wrappedSelf, *args, **kwargs):
|
|
74
|
+
unbound = self._callback
|
|
75
|
+
self._callback = self._callback.__get__(wrappedSelf)
|
|
76
|
+
try:
|
|
77
|
+
with self:
|
|
78
|
+
return funct(wrappedSelf, *args, **kwargs)
|
|
79
|
+
finally:
|
|
80
|
+
self._callback = unbound
|
|
81
|
+
|
|
82
|
+
return wrapper
|
|
83
|
+
|
|
84
|
+
def __enter__(self):
|
|
85
|
+
type(self).__reports__.append((self._title, self._callback))
|
|
86
|
+
|
|
87
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
88
|
+
# If exc_type is None, then no exception was raised, so we should remove the
|
|
89
|
+
# callback. If cls.enabled is False, then nothing has set itself up to call
|
|
90
|
+
# clearReports. We need to remove the callback so it doesn't stay in memory.
|
|
91
|
+
if exc_type is None or not type(self).enabled:
|
|
92
|
+
type(self).__reports__.remove((self._title, self._callback))
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def clearReports(cls):
|
|
96
|
+
"""Removes all of the currently stored callbacks.
|
|
97
|
+
|
|
98
|
+
This should be called after all error reporting is finished, or if a error
|
|
99
|
+
happened and there is nothing to report it. If you set cls.enabled to True,
|
|
100
|
+
something in excepthook should call this to prevent keeping refrences to
|
|
101
|
+
functions from staying in memory.
|
|
102
|
+
"""
|
|
103
|
+
cls.__reports__ = []
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def generateReport(cls, fmt='{result}'):
|
|
107
|
+
"""Executes and returns all of the currently stored callbacks.
|
|
108
|
+
Args:
|
|
109
|
+
|
|
110
|
+
ftm (str, Optional): The results of the callbacks will be inserted into this
|
|
111
|
+
string using str.format into {results}.
|
|
112
|
+
Returns:
|
|
113
|
+
list: A list of tuples for all active ErrorReport classes. The tuples
|
|
114
|
+
contain two strings; the title string, and result of the passed in
|
|
115
|
+
callback function.
|
|
116
|
+
"""
|
|
117
|
+
ret = []
|
|
118
|
+
for title, callback in cls.__reports__:
|
|
119
|
+
result = callback()
|
|
120
|
+
ret.append((title, fmt.format(result=result)))
|
|
121
|
+
return ret
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@contextmanager
|
|
125
|
+
def OverrideConsoleStreams(
|
|
126
|
+
consoles,
|
|
127
|
+
tracebacks=None,
|
|
128
|
+
stdout=None,
|
|
129
|
+
stderr=None,
|
|
130
|
+
result=None,
|
|
131
|
+
logging_handlers=None,
|
|
132
|
+
):
|
|
133
|
+
"""Override a Console's stream and logging_handlers settings.
|
|
134
|
+
|
|
135
|
+
This is useful if you don't want to show tracebacks or output for un-related code.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
|
|
139
|
+
console = OutputConsole()
|
|
140
|
+
with OverrideConsoleStreams(console, tracebacks=True)
|
|
141
|
+
# A traceback raised here would show in console
|
|
142
|
+
do_work_you_only_want_to_show_tracebacks_in_console()
|
|
143
|
+
raise RuntimeError("This traceback will not be shown in console")
|
|
144
|
+
"""
|
|
145
|
+
# If a single console was passed, convert it into a list
|
|
146
|
+
try:
|
|
147
|
+
iter(consoles)
|
|
148
|
+
except TypeError:
|
|
149
|
+
consoles = [consoles]
|
|
150
|
+
|
|
151
|
+
# Store the current state and apply changes to the consoles
|
|
152
|
+
current = []
|
|
153
|
+
for console in consoles:
|
|
154
|
+
current.append(
|
|
155
|
+
(
|
|
156
|
+
console,
|
|
157
|
+
None if tracebacks is None else console.stream_echo_tracebacks,
|
|
158
|
+
None if stdout is None else console.stream_echo_stdout,
|
|
159
|
+
None if stderr is None else console.stream_echo_stderr,
|
|
160
|
+
None if result is None else console.stream_echo_result,
|
|
161
|
+
None if logging_handlers is None else console.logging_handlers,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if tracebacks is not None:
|
|
166
|
+
# Enable/disable traceback display override
|
|
167
|
+
console.stream_echo_tracebacks = tracebacks
|
|
168
|
+
if tracebacks:
|
|
169
|
+
# traceback display is enabled, make it automatically remove
|
|
170
|
+
# itself if an exception is raised by the sys.excepthook handler.
|
|
171
|
+
console._write_error_self_destruct = True
|
|
172
|
+
if stdout is not None:
|
|
173
|
+
console.stream_echo_stdout = stdout
|
|
174
|
+
if stderr is not None:
|
|
175
|
+
console.stream_echo_stderr = stderr
|
|
176
|
+
if result is not None:
|
|
177
|
+
console.stream_echo_result = result
|
|
178
|
+
if logging_handlers is not None:
|
|
179
|
+
console.logging_handlers = logging_handlers
|
|
180
|
+
|
|
181
|
+
# NOTE: This code is a horrible abomination of necessity.
|
|
182
|
+
try:
|
|
183
|
+
yield
|
|
184
|
+
except Exception:
|
|
185
|
+
# This is syntactically needed, we don't need to capture the exception,
|
|
186
|
+
# but the else requires it. We need raise any un-handled exceptions
|
|
187
|
+
# encountered to allow sys.excepthook to process normally.
|
|
188
|
+
# This prevents the else code from removing the traceback print overriding
|
|
189
|
+
# because the else/finally are processed before sys.excepthook is.
|
|
190
|
+
raise
|
|
191
|
+
else:
|
|
192
|
+
# Only called when exception was not raised. Restore the original traceback
|
|
193
|
+
# and disable the self destructing flag its not needed anymore.
|
|
194
|
+
for item in current:
|
|
195
|
+
if tracebacks is not None:
|
|
196
|
+
item[0].stream_echo_tracebacks = item[1]
|
|
197
|
+
if tracebacks:
|
|
198
|
+
item[0]._write_error_self_destruct = False
|
|
199
|
+
finally:
|
|
200
|
+
# Always remove non-traceback options. This is handled before excepthook
|
|
201
|
+
# so we can just use the simplicity of a normal try/finally statement.
|
|
202
|
+
for item in current:
|
|
203
|
+
if stdout is not None:
|
|
204
|
+
item[0].stream_echo_stdout = item[2]
|
|
205
|
+
if stderr is not None:
|
|
206
|
+
item[0].stream_echo_stderr = item[3]
|
|
207
|
+
if result is not None:
|
|
208
|
+
item[0].stream_echo_result = item[4]
|
|
209
|
+
if logging_handlers is not None:
|
|
210
|
+
item[0].logging_handlers = item[5]
|
|
File without changes
|
preditor/cores/core.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
from Qt.QtCore import QObject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Core(QObject):
|
|
7
|
+
"""
|
|
8
|
+
The Core class provides all the main shared functionality and signals that need to
|
|
9
|
+
be distributed between different pacakges.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, objectName=None):
|
|
13
|
+
super(Core, self).__init__()
|
|
14
|
+
if objectName is None:
|
|
15
|
+
objectName = 'PrEditor'
|
|
16
|
+
self.setObjectName(objectName)
|
|
17
|
+
|
|
18
|
+
# Paths in this variable will be removed in
|
|
19
|
+
# preditor.osystem.subprocessEnvironment
|
|
20
|
+
self._removeFromPATHEnv = set()
|
preditor/dccs/.hab.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
+ PrEditor DEVELOPMENT .
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Maya Integration
|
|
2
|
+
|
|
3
|
+
This is an example of using an Maya module to add PrEditor into Maya. This adds
|
|
4
|
+
a PrEditor menu with a PrEditor action in Maya's menu bar letting you open PrEditor. It
|
|
5
|
+
adds the excepthook so if a python exception is raised it will prompt the user
|
|
6
|
+
to show PrEditor. PrEditor will show all python stdout/stderr output generated
|
|
7
|
+
after the plugin is loaded.
|
|
8
|
+
|
|
9
|
+
# Setup
|
|
10
|
+
|
|
11
|
+
Make sure to follow these [setup instructions](/preditor/README.md#Setup) first to create the virtualenv.
|
|
12
|
+
|
|
13
|
+
Alternatively you can use [myapy's](https://help.autodesk.com/view/MAYAUL/2026/ENU/?guid=GUID-72A245EC-CDB4-46AB-BEE0-4BBBF9791627) pip to install the requirements, but a
|
|
14
|
+
separate virtualenv is recommended. This method should not require setting the
|
|
15
|
+
`PREDITOR_SITE` environment variable even if you use an editable install.
|
|
16
|
+
|
|
17
|
+
# Use
|
|
18
|
+
|
|
19
|
+
The [preditor/dccs/maya](/preditor/dccs/maya) directory is setup as a Maya Module. To load it in
|
|
20
|
+
maya add the full path to that directory to the `MAYA_MODULE_PATH` environment
|
|
21
|
+
variable. You can use `;` on windows and `:` on linux to join multiple paths together.
|
|
22
|
+
You will need to enable auto load for the `PrEditor_maya.py` plugin.
|