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/settings.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import absolute_import, print_function
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import configparser
|
|
9
|
+
except Exception:
|
|
10
|
+
import ConfigParser as configparser # noqa: N813
|
|
11
|
+
|
|
12
|
+
# define the default environment variables
|
|
13
|
+
OS_TYPE = ''
|
|
14
|
+
if os.name == 'posix':
|
|
15
|
+
OS_TYPE = 'Linux'
|
|
16
|
+
elif os.name == 'nt':
|
|
17
|
+
OS_TYPE = 'Windows'
|
|
18
|
+
elif os.name == 'osx':
|
|
19
|
+
OS_TYPE = 'MacOS'
|
|
20
|
+
|
|
21
|
+
# The sections to add from settings.ini
|
|
22
|
+
# The order matters. Add from most specific to least specific.
|
|
23
|
+
# Example: Add Windows Offline, then Windows, then Default.
|
|
24
|
+
# Environment variables that exist in os.environ will not be added.
|
|
25
|
+
_SECTIONS_TO_ADD = []
|
|
26
|
+
if os.getenv('BDEV_OFFLINE') == '1':
|
|
27
|
+
_SECTIONS_TO_ADD.append('{} Offline'.format(OS_TYPE))
|
|
28
|
+
_SECTIONS_TO_ADD += [OS_TYPE, 'Default']
|
|
29
|
+
|
|
30
|
+
_currentEnv = ''
|
|
31
|
+
defaults = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def environStr(value):
|
|
35
|
+
if sys.version_info[0] > 2:
|
|
36
|
+
# Python 3 requires a unicode value. aka str(), which these values already are
|
|
37
|
+
return value
|
|
38
|
+
# Python 2 requires str object, not unicode
|
|
39
|
+
return value.encode('utf8')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def addConfigSection(config_parser, section):
|
|
43
|
+
"""
|
|
44
|
+
Add a config section to os.environ for a section.
|
|
45
|
+
|
|
46
|
+
Does not add options that already exist in os.environ.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
config_parser (configparser.RawConfigParser): The parser to read from.
|
|
50
|
+
Must already be read.
|
|
51
|
+
section (str): The section name to add.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
for option in config_parser.options(section):
|
|
55
|
+
if option.upper() not in os.environ:
|
|
56
|
+
value = config_parser.get(section, option)
|
|
57
|
+
if value == 'None':
|
|
58
|
+
value = ''
|
|
59
|
+
# In python2.7 on windows you can't pass unicode values to
|
|
60
|
+
# subprocess.Popen's env argument. This is the reason we are calling str()
|
|
61
|
+
os.environ[environStr(option.upper())] = environStr(value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# load the default environment from the settings INI
|
|
65
|
+
config = configparser.RawConfigParser()
|
|
66
|
+
config.read(os.path.join(os.path.dirname(__file__), 'resource', 'settings.ini'))
|
|
67
|
+
for section in _SECTIONS_TO_ADD:
|
|
68
|
+
addConfigSection(config, section)
|
|
69
|
+
|
|
70
|
+
# store the blurdev path in the environment
|
|
71
|
+
os.environ['BDEV_PATH'] = environStr(os.path.dirname(__file__))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
""" A system for capturing stream output and later inserting the captured output into a
|
|
2
|
+
gui, and allowing that GUI to directly capture any new output written to the streams.
|
|
3
|
+
|
|
4
|
+
A use case for this is to attach a Manager's as early as possible to ``sys.stdout``
|
|
5
|
+
and ``sys.stderr`` process, before any GUI's are created. Then later you initialize a
|
|
6
|
+
GUI python console that should show all python output that has already been written.
|
|
7
|
+
Any future writes are delivered directly to the gui using a callback mechanism.
|
|
8
|
+
|
|
9
|
+
Example::
|
|
10
|
+
|
|
11
|
+
# Startup script or plugin for DCC runs this as early as possible.
|
|
12
|
+
from preditor.stream import install_to_std
|
|
13
|
+
|
|
14
|
+
manager = install_to_std()
|
|
15
|
+
# Startup script exits and DCC continues to load eventually creating the main gui.
|
|
16
|
+
|
|
17
|
+
# From a menu a user chooses to show a custom console(A gui that replicates a
|
|
18
|
+
# python interactive console inside the DCC).
|
|
19
|
+
console = PythonConsole()
|
|
20
|
+
|
|
21
|
+
# We will want to see all the previous writes made by python, so replay them
|
|
22
|
+
# in the console so it can properly handle stdout and stderr writes.
|
|
23
|
+
for msg, state in manager:
|
|
24
|
+
console.write(msg, state)
|
|
25
|
+
|
|
26
|
+
# Make it so any future writes are automatically added to the console.
|
|
27
|
+
manager.add_callback(console.write)
|
|
28
|
+
|
|
29
|
+
# Optionally, disable storing data in the buffer. buffer.write calls will now
|
|
30
|
+
# directly write to console so there is no reason to duplicate the data to the
|
|
31
|
+
# buffer.
|
|
32
|
+
manager.append_writes = False
|
|
33
|
+
"""
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
from ..constants import StreamType
|
|
37
|
+
from ..streamhandler_helper import StreamHandlerHelper # noqa: E402
|
|
38
|
+
from .director import Director # noqa: E402
|
|
39
|
+
from .manager import Manager # noqa: E402
|
|
40
|
+
|
|
41
|
+
"""Set when :py:attr:``install_to_std`` is called. This stores the installed Manager
|
|
42
|
+
so it can be accessed to install callbacks.
|
|
43
|
+
"""
|
|
44
|
+
active = None
|
|
45
|
+
|
|
46
|
+
__all__ = ["active", "Director", "install_to_std", "Manager"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def install_to_std(out=True, err=True):
|
|
50
|
+
"""Replaces ``sys.stdout`` and ``sys.stderr`` with :py:class:`Director`'s
|
|
51
|
+
using the returned :py:class:`Manager`. This manager is stored as the ``active``
|
|
52
|
+
variable and can be accessed later. This can be called more than once, and it will
|
|
53
|
+
simply return the already installed Manager.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
out (bool, optional): Enables replacement of ``sys.stdout`` on first call.
|
|
57
|
+
err (bool, optional): Enables replacement of ``sys.stderr`` on first call.
|
|
58
|
+
"""
|
|
59
|
+
global active
|
|
60
|
+
|
|
61
|
+
if active is None:
|
|
62
|
+
active = Manager()
|
|
63
|
+
if out:
|
|
64
|
+
sys.stdout = Director(active, StreamType.STDOUT)
|
|
65
|
+
# Update any StreamHandler's that were setup using the old stdout
|
|
66
|
+
StreamHandlerHelper.replace_stream(sys.stdout.old_stream, sys.stdout)
|
|
67
|
+
if err:
|
|
68
|
+
sys.stderr = Director(active, StreamType.STDERR)
|
|
69
|
+
# Update any StreamHandler's that were setup using the old stderr
|
|
70
|
+
StreamHandlerHelper.replace_stream(sys.stderr.old_stream, sys.stderr)
|
|
71
|
+
|
|
72
|
+
return active
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .. import plugins
|
|
9
|
+
from ..constants import StreamType
|
|
10
|
+
from . import Manager
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DefaultDescriptor:
|
|
16
|
+
def __init__(self, *, ident=None, default=None):
|
|
17
|
+
self._default = default
|
|
18
|
+
self._ident = ident
|
|
19
|
+
|
|
20
|
+
def __set_name__(self, owner, name):
|
|
21
|
+
self._name = "_" + name
|
|
22
|
+
|
|
23
|
+
def __get__(self, obj, type):
|
|
24
|
+
if obj is None:
|
|
25
|
+
return self._default
|
|
26
|
+
|
|
27
|
+
return getattr(obj, self._name, self._default)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LoggingLevelDescriptor(DefaultDescriptor):
|
|
31
|
+
"""Converts a string into a logging level or None.
|
|
32
|
+
|
|
33
|
+
When set, the string will be converted to an int if possible otherwise the
|
|
34
|
+
provided value is stored.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_level_conversion = logging.Handler()
|
|
38
|
+
|
|
39
|
+
def __set__(self, obj, value):
|
|
40
|
+
if value is None:
|
|
41
|
+
value = logging.NOTSET
|
|
42
|
+
else:
|
|
43
|
+
try:
|
|
44
|
+
value = int(value)
|
|
45
|
+
except ValueError:
|
|
46
|
+
# convert logging level strings to their int values if possible.
|
|
47
|
+
try:
|
|
48
|
+
self._level_conversion.setLevel(value)
|
|
49
|
+
value = self._level_conversion.level
|
|
50
|
+
except Exception:
|
|
51
|
+
logger.warning(f"Unable to convert {value} to an int logging level")
|
|
52
|
+
value = 0
|
|
53
|
+
setattr(obj, self._name, value)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FormatterDescriptor(DefaultDescriptor):
|
|
57
|
+
"""Stores a logging.Formatter object.
|
|
58
|
+
|
|
59
|
+
If a string is passed it will be cast into a Formatter instance.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, *, ident=None, default=None):
|
|
63
|
+
if isinstance(default, str):
|
|
64
|
+
default = logging.Formatter(default)
|
|
65
|
+
super().__init__(ident=ident, default=default)
|
|
66
|
+
|
|
67
|
+
def __set__(self, obj, value):
|
|
68
|
+
if isinstance(value, str):
|
|
69
|
+
value = logging.Formatter(value)
|
|
70
|
+
setattr(obj, self._name, value)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class HandlerInfo:
|
|
75
|
+
name: str
|
|
76
|
+
level: LoggingLevelDescriptor = LoggingLevelDescriptor()
|
|
77
|
+
plugin: Optional[str] = "Console"
|
|
78
|
+
formatter: FormatterDescriptor = FormatterDescriptor()
|
|
79
|
+
|
|
80
|
+
_attr_names = {"plug": "plugin", "fmt": "formatter", "lvl": "level"}
|
|
81
|
+
|
|
82
|
+
def __post_init__(self):
|
|
83
|
+
# Clear self.name so you can define omit name to define a root logger
|
|
84
|
+
# For example passing "level=INFO" would set the root logger to info.
|
|
85
|
+
name = self.name
|
|
86
|
+
self.name = ""
|
|
87
|
+
|
|
88
|
+
parts = self.__to_parts__(name)
|
|
89
|
+
for i, value in enumerate(parts):
|
|
90
|
+
key, _value = self.__parse_setting__(value, i)
|
|
91
|
+
setattr(self, key, _value)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def __to_parts__(cls, value):
|
|
95
|
+
"""Returns a list of args and kwargs. Kwargs are 2 item tuples."""
|
|
96
|
+
sp = re.split(r'(\w+(?<!\\)=)', value)
|
|
97
|
+
args = [i for i in sp[0].split(',') if i]
|
|
98
|
+
for i in range(1, len(sp), 2):
|
|
99
|
+
value = sp[i + 1].replace("\\=", "=")
|
|
100
|
+
if value.endswith(","):
|
|
101
|
+
value = value[:-1]
|
|
102
|
+
args.append((sp[i][:-1], value))
|
|
103
|
+
return args
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def __parse_setting__(cls, value, index):
|
|
107
|
+
"""Converts a value into its name and value.
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
("preditor", 0) returns ("name", "preditor")
|
|
111
|
+
("lvl=DEBUG", 0) returns ("level", "INFO")
|
|
112
|
+
"""
|
|
113
|
+
if isinstance(value, tuple):
|
|
114
|
+
# The string specified the key so expand it to the property name.
|
|
115
|
+
attr_name = cls._attr_names.get(value[0], value[0])
|
|
116
|
+
value = value[1]
|
|
117
|
+
else:
|
|
118
|
+
# If there is no key defined, map the index to the property name
|
|
119
|
+
i = list(cls.__dataclass_fields__)[index]
|
|
120
|
+
field = cls.__dataclass_fields__[i]
|
|
121
|
+
attr_name = field.name
|
|
122
|
+
|
|
123
|
+
return attr_name, value
|
|
124
|
+
|
|
125
|
+
def install(self, callback=None, replay=False, disable_writes=False, clear=False):
|
|
126
|
+
"""Add the required logging handler if needed and connect callback to it."""
|
|
127
|
+
_logger = logging.getLogger(self.name)
|
|
128
|
+
handler, _ = plugins.add_logging_handler(_logger, self.plugin)
|
|
129
|
+
if handler and callback:
|
|
130
|
+
handler.manager.add_callback(
|
|
131
|
+
callback, replay=replay, disable_writes=disable_writes, clear=clear
|
|
132
|
+
)
|
|
133
|
+
return handler
|
|
134
|
+
|
|
135
|
+
def uninstall(self, callback):
|
|
136
|
+
"""Remove the callback added via install, doesn't remove the logging handler."""
|
|
137
|
+
_logger = logging.getLogger(self.name)
|
|
138
|
+
handler, _ = plugins.add_logging_handler(_logger, self.plugin)
|
|
139
|
+
if handler:
|
|
140
|
+
handler.manager.remove_callback(callback)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ConsoleHandler(logging.Handler):
|
|
144
|
+
"""A logging handler that writes directly to the PrEditor instance.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
formatter (str or logging.Formatter, optional): If specified,
|
|
148
|
+
this is passed to setFormatter.
|
|
149
|
+
stream (optional): If provided write to this stream instead of the
|
|
150
|
+
main preditor instance's console.
|
|
151
|
+
stream_type (StreamType, optional): If not None, pass this value to the
|
|
152
|
+
write call's force kwarg.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self):
|
|
156
|
+
super().__init__()
|
|
157
|
+
self.manager = Manager()
|
|
158
|
+
|
|
159
|
+
def emit(self, record):
|
|
160
|
+
try:
|
|
161
|
+
# If no gui has been created yet, or the `preditor.instance()` was
|
|
162
|
+
# closed and garbage collected, there is nothing to do, simply exit
|
|
163
|
+
# msg = self.format(record)
|
|
164
|
+
# self.manager.write(f'{msg}\n', StreamType.CONSOLE)
|
|
165
|
+
self.manager.write((self, record), StreamType.CONSOLE)
|
|
166
|
+
except (KeyboardInterrupt, SystemExit):
|
|
167
|
+
raise
|
|
168
|
+
except Exception:
|
|
169
|
+
self.handleError(record)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from ..constants import StreamType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _DirectorBuffer(io.RawIOBase):
|
|
10
|
+
"""Binary buffer that forwards text writes to the manager.
|
|
11
|
+
|
|
12
|
+
This makes the stream more compatible including if enabled when running tox.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
manager (Manager): The manager that writes are stored in.
|
|
16
|
+
state: The state passed to the manager. This is often``StreamType.STDOUT``
|
|
17
|
+
or ``StreamType.STDERR``.
|
|
18
|
+
old_stream: A second stream that will be written to every time this stream
|
|
19
|
+
is written to. This allows this object to replace sys.stdout and still
|
|
20
|
+
send that output to the original stdout, which is useful for not breaking
|
|
21
|
+
DCC's script editors. Pass False to disable this feature. If you pass None
|
|
22
|
+
and state is set to ``StreamType.STDOUT`` or ``StreamType.STDERR``
|
|
23
|
+
this will automatically be set to the current sys.stdout or sys.stderr.
|
|
24
|
+
name (str, optional): Stored on self.name.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, manager, state, old_stream=None, name='nul'):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.manager = manager
|
|
30
|
+
self.state = state
|
|
31
|
+
self.old_stream = old_stream
|
|
32
|
+
self.name = name
|
|
33
|
+
|
|
34
|
+
def flush(self):
|
|
35
|
+
if self.old_stream:
|
|
36
|
+
self.old_stream.flush()
|
|
37
|
+
super().flush()
|
|
38
|
+
|
|
39
|
+
def writable(self):
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
def write(self, b):
|
|
43
|
+
if isinstance(b, memoryview):
|
|
44
|
+
b = b.tobytes()
|
|
45
|
+
|
|
46
|
+
# Decode incoming bytes (TextIOWrapper encodes before sending here)
|
|
47
|
+
msg = b.decode("utf-8", errors="replace")
|
|
48
|
+
self.manager.write(msg, self.state)
|
|
49
|
+
|
|
50
|
+
if self.old_stream:
|
|
51
|
+
self.old_stream.write(msg)
|
|
52
|
+
|
|
53
|
+
return len(b)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Director(io.TextIOWrapper):
|
|
57
|
+
"""A file like object that stores the text written to it in a manager.
|
|
58
|
+
This manager can be shared between multiple Directors to build a single
|
|
59
|
+
continuous history of all writes.
|
|
60
|
+
|
|
61
|
+
While this uses a buffer under the hood, buffering is disabled and any calls
|
|
62
|
+
to write will automatically flush the buffer.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
manager (Manager): The manager that writes are stored in.
|
|
66
|
+
state: The state passed to the manager. This is often ``StreamType.STDOUT``
|
|
67
|
+
or ``StreamType.STDERR``.
|
|
68
|
+
old_stream: A second stream that will be written to every time this stream
|
|
69
|
+
is written to. This allows this object to replace sys.stdout and still
|
|
70
|
+
send that output to the original stdout, which is useful for not breaking
|
|
71
|
+
DCC's script editors. Pass False to disable this feature. If you pass None
|
|
72
|
+
and state is set to ``StreamType.STDOUT`` or ``StreamType.STDERR``
|
|
73
|
+
this will automatically be set to the current sys.stdout or sys.stderr.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, manager, state, old_stream=None, *args, **kwargs):
|
|
77
|
+
# Keep track of whether we wrapped a std stream
|
|
78
|
+
# that way we don't .close() any streams that we don't control
|
|
79
|
+
self.std_stream_wrapped = False
|
|
80
|
+
|
|
81
|
+
name = 'nul'
|
|
82
|
+
if old_stream is False:
|
|
83
|
+
old_stream = None
|
|
84
|
+
elif old_stream is None:
|
|
85
|
+
if state == StreamType.STDOUT:
|
|
86
|
+
# On Windows if we're in pythonw.exe, then sys.stdout is named "nul"
|
|
87
|
+
# And it uses cp1252 encoding (which breaks with unicode)
|
|
88
|
+
# So if we find this nul TextIOWrapper, it's safe to just skip it
|
|
89
|
+
name = getattr(sys.stdout, 'name', '')
|
|
90
|
+
if name != 'nul':
|
|
91
|
+
self.std_stream_wrapped = True
|
|
92
|
+
old_stream = sys.stdout
|
|
93
|
+
elif state == StreamType.STDERR:
|
|
94
|
+
name = getattr(sys.stderr, 'name', '')
|
|
95
|
+
if name != 'nul':
|
|
96
|
+
self.std_stream_wrapped = True
|
|
97
|
+
old_stream = sys.stderr
|
|
98
|
+
|
|
99
|
+
self.old_stream = old_stream
|
|
100
|
+
self.manager = manager
|
|
101
|
+
self.state = state
|
|
102
|
+
|
|
103
|
+
# Build the buffer. This provides the expected interface for tox, etc.
|
|
104
|
+
raw = _DirectorBuffer(manager, state, old_stream, name)
|
|
105
|
+
buffer = io.BufferedWriter(raw)
|
|
106
|
+
|
|
107
|
+
super().__init__(buffer, encoding="utf-8", write_through=True, *args, **kwargs)
|
|
108
|
+
|
|
109
|
+
def __repr__(self):
|
|
110
|
+
return f"<Director state={self.state} old_stream={self.old_stream!r}>"
|
|
111
|
+
|
|
112
|
+
def close(self):
|
|
113
|
+
if (
|
|
114
|
+
self.old_stream
|
|
115
|
+
and not self.std_stream_wrapped
|
|
116
|
+
and self.old_stream is not sys.__stdout__
|
|
117
|
+
and self.old_stream is not sys.__stderr__
|
|
118
|
+
):
|
|
119
|
+
self.old_stream.close()
|
|
120
|
+
|
|
121
|
+
super().close()
|
|
122
|
+
|
|
123
|
+
def write(self, msg):
|
|
124
|
+
super().write(msg)
|
|
125
|
+
# Force a write of any buffered data
|
|
126
|
+
self.flush()
|
|
127
|
+
|
|
128
|
+
# These methods enable terminal features like color coding etc.
|
|
129
|
+
def isatty(self):
|
|
130
|
+
if self.old_stream is not None:
|
|
131
|
+
return self.old_stream.isatty()
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def encoding(self):
|
|
136
|
+
if self.old_stream is not None:
|
|
137
|
+
return self.old_stream.encoding
|
|
138
|
+
return super().encoding
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def errors(self):
|
|
142
|
+
if self.old_stream is not None:
|
|
143
|
+
return self.old_stream.errors
|
|
144
|
+
return super().errors
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
|
|
5
|
+
from .. import utils
|
|
6
|
+
from ..weakref import WeakList
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Manager(collections.deque):
|
|
10
|
+
"""Stores all of the data from the stdout/stderr writes. You can iterate over this
|
|
11
|
+
object to see all of the (msg, state) calls that have been written to it up to the
|
|
12
|
+
maxlen specified when constructing it.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
maxlen (int, optional): The maximum number of raw writes to store. If this is
|
|
16
|
+
exceeded, the oldest writes are discarded.
|
|
17
|
+
|
|
18
|
+
Properties:
|
|
19
|
+
store_writes (bool): Set this to False if you no longer want write calls to
|
|
20
|
+
store on the manager.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, maxlen=10000):
|
|
24
|
+
super(Manager, self).__init__(maxlen=maxlen)
|
|
25
|
+
self.callbacks = WeakList()
|
|
26
|
+
self.store_writes = True
|
|
27
|
+
|
|
28
|
+
def add_callback(self, callback, replay=False, disable_writes=False, clear=False):
|
|
29
|
+
"""Add a callable that will be called every time write is called.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
callback (callable): A callable object that takes two arguments. It must
|
|
33
|
+
take two arguments (msg, state). See write for more details.
|
|
34
|
+
replay (bool, optional): If True, calls replay on callback.
|
|
35
|
+
disable_writes (bool, optional): Set store_writes to False if this is True.
|
|
36
|
+
clear (bool, optional): Clear the stored history on this object.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
bool: True if the callback was added. If the callback has already been
|
|
40
|
+
already, this method does nothing and returns False.
|
|
41
|
+
"""
|
|
42
|
+
if callback in self.callbacks:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
self.callbacks.append(callback)
|
|
46
|
+
|
|
47
|
+
if replay:
|
|
48
|
+
self.replay(callback)
|
|
49
|
+
|
|
50
|
+
if disable_writes:
|
|
51
|
+
# Disable storing data in the buffer. buffer.write calls will now
|
|
52
|
+
# directly write to console so there is no reason to duplicate the
|
|
53
|
+
# data to the buffer.
|
|
54
|
+
self.store_writes = False
|
|
55
|
+
|
|
56
|
+
if clear:
|
|
57
|
+
self.clear()
|
|
58
|
+
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def remove_callback(self, callback) -> bool:
|
|
62
|
+
"""Remove callback from manager and return if it was removed."""
|
|
63
|
+
if callback not in self.callbacks:
|
|
64
|
+
return False
|
|
65
|
+
self.callbacks.remove(callback)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def replay(self, callback):
|
|
69
|
+
"""Replay the existing writes for the given callback.
|
|
70
|
+
|
|
71
|
+
This iterates over all the stored writes and pass them to callback. This
|
|
72
|
+
is useful for when you are initializing a gui and want to include all
|
|
73
|
+
previous prints.
|
|
74
|
+
"""
|
|
75
|
+
for msg, state in self:
|
|
76
|
+
callback(msg, state)
|
|
77
|
+
|
|
78
|
+
def get_value(self, fmt="[{state}:{msg}]"):
|
|
79
|
+
return ''.join([fmt.format(msg=d[0], state=d[1]) for d in self])
|
|
80
|
+
|
|
81
|
+
def write(self, msg, state):
|
|
82
|
+
"""Adds the written text to the manager and passes it to any attached callbacks.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
msg (str): The text to be written.
|
|
86
|
+
state: A identifier for how the text is to be written. For example if this
|
|
87
|
+
write is coming from sys.stderr this will likely be set to
|
|
88
|
+
``preditor.constants.StreamType.STDERR``.
|
|
89
|
+
"""
|
|
90
|
+
if self.store_writes:
|
|
91
|
+
self.append((msg, state))
|
|
92
|
+
|
|
93
|
+
for callback in self.callbacks:
|
|
94
|
+
try:
|
|
95
|
+
callback(msg, state)
|
|
96
|
+
except Exception:
|
|
97
|
+
utils.ShellPrint(True).print_exc("PrEditor Console failed")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StreamHandlerHelper(object):
|
|
8
|
+
"""A collection of functions for manipulating ``logging.StreamHandler`` objects."""
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def set_stream(cls, handler, stream):
|
|
12
|
+
"""For the given StreamHandler, set its stream. This works around
|
|
13
|
+
python 2's lack of StreamHandler.setStream by replicating python 3.
|
|
14
|
+
"""
|
|
15
|
+
# TODO: once python 2 is no longer supported, replace any uses of this
|
|
16
|
+
# function with `handler.setStream(stream)`
|
|
17
|
+
if sys.version_info[0] > 2:
|
|
18
|
+
handler.setStream(stream)
|
|
19
|
+
else:
|
|
20
|
+
# Copied from python 3's logging's setStream to work in python 2
|
|
21
|
+
handler.acquire()
|
|
22
|
+
try:
|
|
23
|
+
handler.flush()
|
|
24
|
+
handler.stream = stream
|
|
25
|
+
finally:
|
|
26
|
+
handler.release()
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def replace_stream(cls, old, new, logger=None):
|
|
30
|
+
"""Replaces the stream of StreamHandlers by checking all
|
|
31
|
+
`logging.StreamHandler`'s attached to the provided logger. If any of them are
|
|
32
|
+
using old for their stream, update that stream to new.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
old (stream): Only StreamHandlers using this stream will be updated to new.
|
|
36
|
+
new (stream): A file stream object like `sys.stderr` that will replace old.
|
|
37
|
+
logger (logging.Logger, optional): The logger to update streams for. If
|
|
38
|
+
None, the root logger(`logging.getLogger()`) will be used.
|
|
39
|
+
"""
|
|
40
|
+
if logger is None:
|
|
41
|
+
logger = logging.getLogger()
|
|
42
|
+
|
|
43
|
+
for handler in logger.handlers:
|
|
44
|
+
if isinstance(handler, logging.StreamHandler):
|
|
45
|
+
if handler.stream == old:
|
|
46
|
+
cls.set_stream(handler, new)
|