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.
Files changed (179) hide show
  1. preditor/__init__.py +315 -0
  2. preditor/__main__.py +13 -0
  3. preditor/about_module.py +165 -0
  4. preditor/cli.py +192 -0
  5. preditor/config.py +318 -0
  6. preditor/constants.py +13 -0
  7. preditor/contexts.py +210 -0
  8. preditor/cores/__init__.py +0 -0
  9. preditor/cores/core.py +20 -0
  10. preditor/dccs/.hab.json +10 -0
  11. preditor/dccs/maya/PrEditor_maya.mod +1 -0
  12. preditor/dccs/maya/README.md +22 -0
  13. preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
  14. preditor/dccs/studiomax/PackageContents.xml +32 -0
  15. preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
  16. preditor/dccs/studiomax/README.md +17 -0
  17. preditor/dccs/studiomax/preditor.ms +16 -0
  18. preditor/dccs/studiomax/preditor_menu.mnx +7 -0
  19. preditor/debug.py +149 -0
  20. preditor/delayable_engine/__init__.py +302 -0
  21. preditor/delayable_engine/delayables.py +85 -0
  22. preditor/enum.py +728 -0
  23. preditor/excepthooks.py +165 -0
  24. preditor/gui/__init__.py +56 -0
  25. preditor/gui/app.py +163 -0
  26. preditor/gui/codehighlighter.py +289 -0
  27. preditor/gui/completer.py +237 -0
  28. preditor/gui/console.py +605 -0
  29. preditor/gui/console_base.py +911 -0
  30. preditor/gui/dialog.py +181 -0
  31. preditor/gui/drag_tab_bar.py +625 -0
  32. preditor/gui/editor_chooser.py +57 -0
  33. preditor/gui/errordialog.py +69 -0
  34. preditor/gui/find_files.py +137 -0
  35. preditor/gui/fuzzy_search/__init__.py +0 -0
  36. preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
  37. preditor/gui/group_tab_widget/__init__.py +0 -0
  38. preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
  39. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  40. preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
  41. preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
  42. preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
  43. preditor/gui/level_buttons.py +358 -0
  44. preditor/gui/logger_window_handler.py +77 -0
  45. preditor/gui/logger_window_plugin.py +35 -0
  46. preditor/gui/loggerwindow.py +2405 -0
  47. preditor/gui/newtabwidget.py +69 -0
  48. preditor/gui/output_console.py +11 -0
  49. preditor/gui/qtdesigner/__init__.py +21 -0
  50. preditor/gui/qtdesigner/_log_plugin.py +29 -0
  51. preditor/gui/qtdesigner/console_base_plugin.py +48 -0
  52. preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
  53. preditor/gui/set_text_editor_path_dialog.py +61 -0
  54. preditor/gui/status_label.py +99 -0
  55. preditor/gui/suggest_path_quotes_dialog.py +50 -0
  56. preditor/gui/ui/editor_chooser.ui +93 -0
  57. preditor/gui/ui/errordialog.ui +74 -0
  58. preditor/gui/ui/find_files.ui +140 -0
  59. preditor/gui/ui/loggerwindow.ui +1909 -0
  60. preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
  61. preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
  62. preditor/gui/window.py +161 -0
  63. preditor/gui/workbox_mixin.py +1139 -0
  64. preditor/gui/workbox_text_edit.py +136 -0
  65. preditor/gui/workboxwidget.py +315 -0
  66. preditor/logging_config.py +55 -0
  67. preditor/osystem.py +401 -0
  68. preditor/plugins.py +118 -0
  69. preditor/prefs.py +381 -0
  70. preditor/resource/environment_variables.html +26 -0
  71. preditor/resource/error_mail.html +85 -0
  72. preditor/resource/error_mail_inline.html +41 -0
  73. preditor/resource/img/README.md +17 -0
  74. preditor/resource/img/arrow_forward.png +0 -0
  75. preditor/resource/img/check-bold.png +0 -0
  76. preditor/resource/img/chevron-down.png +0 -0
  77. preditor/resource/img/chevron-up.png +0 -0
  78. preditor/resource/img/close-thick.png +0 -0
  79. preditor/resource/img/comment-edit.png +0 -0
  80. preditor/resource/img/content-copy.png +0 -0
  81. preditor/resource/img/content-cut.png +0 -0
  82. preditor/resource/img/content-duplicate.png +0 -0
  83. preditor/resource/img/content-paste.png +0 -0
  84. preditor/resource/img/content-save.png +0 -0
  85. preditor/resource/img/debug_disabled.png +0 -0
  86. preditor/resource/img/eye-check.png +0 -0
  87. preditor/resource/img/file-plus.png +0 -0
  88. preditor/resource/img/file-remove.png +0 -0
  89. preditor/resource/img/format-align-left.png +0 -0
  90. preditor/resource/img/format-letter-case-lower.png +0 -0
  91. preditor/resource/img/format-letter-case-upper.png +0 -0
  92. preditor/resource/img/format-letter-case.svg +1 -0
  93. preditor/resource/img/information.png +0 -0
  94. preditor/resource/img/logging_critical.png +0 -0
  95. preditor/resource/img/logging_custom.png +0 -0
  96. preditor/resource/img/logging_debug.png +0 -0
  97. preditor/resource/img/logging_error.png +0 -0
  98. preditor/resource/img/logging_info.png +0 -0
  99. preditor/resource/img/logging_not_set.png +0 -0
  100. preditor/resource/img/logging_warning.png +0 -0
  101. preditor/resource/img/marker.png +0 -0
  102. preditor/resource/img/play.png +0 -0
  103. preditor/resource/img/playlist-play.png +0 -0
  104. preditor/resource/img/plus-minus-variant.png +0 -0
  105. preditor/resource/img/preditor.ico +0 -0
  106. preditor/resource/img/preditor.png +0 -0
  107. preditor/resource/img/preditor.psd +0 -0
  108. preditor/resource/img/preditor.svg +44 -0
  109. preditor/resource/img/regex.svg +1 -0
  110. preditor/resource/img/restart.svg +1 -0
  111. preditor/resource/img/skip-forward-outline.png +0 -0
  112. preditor/resource/img/skip-next-outline.png +0 -0
  113. preditor/resource/img/skip-next.png +0 -0
  114. preditor/resource/img/skip-previous.png +0 -0
  115. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  116. preditor/resource/img/text-search-variant.png +0 -0
  117. preditor/resource/img/warning-big.png +0 -0
  118. preditor/resource/lang/python.json +30 -0
  119. preditor/resource/pref_updates/pref_updates.json +17 -0
  120. preditor/resource/settings.ini +25 -0
  121. preditor/resource/stylesheet/Bright.css +76 -0
  122. preditor/resource/stylesheet/Dark.css +210 -0
  123. preditor/scintilla/__init__.py +40 -0
  124. preditor/scintilla/delayables/__init__.py +11 -0
  125. preditor/scintilla/delayables/smart_highlight.py +97 -0
  126. preditor/scintilla/delayables/spell_check.py +174 -0
  127. preditor/scintilla/documenteditor.py +1924 -0
  128. preditor/scintilla/finddialog.py +68 -0
  129. preditor/scintilla/lang/__init__.py +80 -0
  130. preditor/scintilla/lang/config/bash.ini +15 -0
  131. preditor/scintilla/lang/config/batch.ini +14 -0
  132. preditor/scintilla/lang/config/cpp.ini +19 -0
  133. preditor/scintilla/lang/config/css.ini +19 -0
  134. preditor/scintilla/lang/config/eyeonscript.ini +17 -0
  135. preditor/scintilla/lang/config/html.ini +21 -0
  136. preditor/scintilla/lang/config/javascript.ini +24 -0
  137. preditor/scintilla/lang/config/lua.ini +16 -0
  138. preditor/scintilla/lang/config/maxscript.ini +20 -0
  139. preditor/scintilla/lang/config/mel.ini +18 -0
  140. preditor/scintilla/lang/config/mu.ini +22 -0
  141. preditor/scintilla/lang/config/nsi.ini +19 -0
  142. preditor/scintilla/lang/config/perl.ini +19 -0
  143. preditor/scintilla/lang/config/puppet.ini +19 -0
  144. preditor/scintilla/lang/config/python.ini +28 -0
  145. preditor/scintilla/lang/config/ruby.ini +19 -0
  146. preditor/scintilla/lang/config/sql.ini +7 -0
  147. preditor/scintilla/lang/config/xml.ini +21 -0
  148. preditor/scintilla/lang/config/yaml.ini +18 -0
  149. preditor/scintilla/lang/language.py +240 -0
  150. preditor/scintilla/lexers/__init__.py +0 -0
  151. preditor/scintilla/lexers/cpplexer.py +22 -0
  152. preditor/scintilla/lexers/javascriptlexer.py +27 -0
  153. preditor/scintilla/lexers/maxscriptlexer.py +235 -0
  154. preditor/scintilla/lexers/mellexer.py +369 -0
  155. preditor/scintilla/lexers/mulexer.py +33 -0
  156. preditor/scintilla/lexers/pythonlexer.py +42 -0
  157. preditor/scintilla/ui/finddialog.ui +160 -0
  158. preditor/settings.py +71 -0
  159. preditor/stream/__init__.py +72 -0
  160. preditor/stream/console_handler.py +169 -0
  161. preditor/stream/director.py +144 -0
  162. preditor/stream/manager.py +97 -0
  163. preditor/streamhandler_helper.py +46 -0
  164. preditor/utils/__init__.py +191 -0
  165. preditor/utils/call_stack.py +86 -0
  166. preditor/utils/cute.py +106 -0
  167. preditor/utils/stylesheets.py +54 -0
  168. preditor/utils/text_search.py +338 -0
  169. preditor/version.py +34 -0
  170. preditor/weakref.py +363 -0
  171. preditor-2.1.0.dist-info/METADATA +308 -0
  172. preditor-2.1.0.dist-info/RECORD +179 -0
  173. preditor-2.1.0.dist-info/WHEEL +5 -0
  174. preditor-2.1.0.dist-info/entry_points.txt +19 -0
  175. preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
  176. preditor-2.1.0.dist-info/top_level.txt +3 -0
  177. tests/encodings/test_ecoding.py +33 -0
  178. tests/find_files/test_find_files.py +74 -0
  179. tests/ide/test_delayable_engine.py +171 -0
@@ -0,0 +1,191 @@
1
+ import errno
2
+ import json
3
+ import os
4
+ import sys
5
+ import traceback
6
+ from pathlib import Path
7
+
8
+
9
+ class Json:
10
+ """Load a json file with a better tracebacks if something goes wrong.
11
+
12
+ Args:
13
+ filename (pathlib.Path): The path to the file being loaded/parsed. Unless
14
+ `json_str` is also provided load will use `json.load` to parse the
15
+ contents of this json file.
16
+ json_str (str, optional): If provided then uses `json.loads` to parse
17
+ this value. `filename` must be provided and will be included in any
18
+ exceptions that are raised parsing this text.
19
+ """
20
+
21
+ def __init__(self, filename, json_str=None):
22
+ if isinstance(filename, str):
23
+ filename = Path(filename)
24
+ self.filename = filename
25
+ self.json_str = json_str
26
+
27
+ @classmethod
28
+ def _load_json(cls, source, load_funct, *args, **kwargs):
29
+ """Work function that parses json and ensures any errors report the source.
30
+
31
+ Args:
32
+ source (os.PathLike or str): The source of the json data. This is
33
+ reported in any raised exceptions.
34
+ load_funct (callable): A function called to parse the json data.
35
+ Normally this is `json.load` or `json.loads`.
36
+ *args: Arguments passed to `load_funct`.
37
+ *kwargs: Keyword arguments passed to `load_funct`.
38
+
39
+ Raises:
40
+ FileNotFoundError: If filename is not pointing to a file that
41
+ actually exists.
42
+ ValueError: The error raised due to invalid json.
43
+ """
44
+ try:
45
+ return load_funct(*args, **kwargs)
46
+ except ValueError as e:
47
+ # Using python's native json parser
48
+ msg = f'{e} Source("{source}")'
49
+ raise type(e)(msg, e.doc, e.pos).with_traceback(sys.exc_info()[2]) from None
50
+
51
+ def load(self):
52
+ """Parse and return self.json_str if defined otherwise self.filename."""
53
+ if self.json_str:
54
+ return self.loads_json(self.json_str, self.filename)
55
+ return self.load_json_file(self.filename)
56
+
57
+ @classmethod
58
+ def load_json_file(cls, filename):
59
+ """Open and parse a json file. If a parsing error happens the file path
60
+ is added to the exception to allow for easier debugging.
61
+
62
+ Args:
63
+ filename (pathlib.Path): A existing file path.
64
+
65
+ Returns:
66
+ The data stored in the json file.
67
+
68
+ Raises:
69
+ FileNotFoundError: If filename is not pointing to a file that
70
+ actually exists.
71
+ ValueError: The error raised due to invalid json.
72
+ """
73
+ if not filename.is_file():
74
+ raise FileNotFoundError(
75
+ errno.ENOENT, os.strerror(errno.ENOENT), str(filename)
76
+ )
77
+
78
+ with filename.open() as fle:
79
+ data = cls._load_json(filename, json.load, fle)
80
+ return data
81
+
82
+ @classmethod
83
+ def loads_json(cls, json_str, source):
84
+ """Open and parse a json string. If a parsing error happens the source
85
+ file path is added to the exception to allow for easier debugging.
86
+
87
+ Args:
88
+ json_str (str): The json data to parse.
89
+ source (pathlib.Path): The location json_str was pulled from.
90
+ This is reported if any parsing errors happen.
91
+
92
+ Returns:
93
+ The data stored in the json file.
94
+
95
+ Raises:
96
+ FileNotFoundError: If filename is not pointing to a file that
97
+ actually exists.
98
+ ValueError: The error raised due to invalid json.
99
+ """
100
+ return cls._load_json(source, json.loads, json_str)
101
+
102
+
103
+ class ShellPrint:
104
+ """Utilities to print to sys.__stdout__/__stderr__ bypassing the PrEditor streams.
105
+
106
+ This allows you to write to the host console instead of PrEditor's interface.
107
+ This helps when developing for the stream or console classes if you cause an
108
+ exception you might get no traceback printed to debug otherwise.
109
+
110
+ On windows if using gui mode app(pythonw.exe) that doesn't have a console
111
+ the methods will not print anything and just return False. If possible switch
112
+ to using python.exe or install another file stream like `preditor.debug.FileLogger`,
113
+ but they will need to be installed on `sys.__stdout__/__stderr__`.
114
+ """
115
+
116
+ def __init__(self, error=False):
117
+ self.error = error
118
+
119
+ def print(self, *args, **kwargs):
120
+ """Prints to the shell."""
121
+ if "file" in kwargs:
122
+ raise KeyError(
123
+ "file can not be passed to `ShellPrint.print`. Instead use error."
124
+ )
125
+ # Check for pythonw.exe's lack of streams and exit
126
+ kwargs["file"] = self.stream
127
+ if kwargs["file"] is None:
128
+ """Note: This protects against errors like this when using pythonw
129
+ File "preditor/stream/director.py", line 124, in write
130
+ super().write(msg)
131
+ RuntimeError: reentrant call inside <_io.BufferedWriter name='nul'>
132
+ """
133
+ return False
134
+
135
+ # Print to the host stream
136
+ print(*args, **kwargs)
137
+ return True
138
+
139
+ def print_exc(self, msg, limit=None, chain=True, width=79):
140
+ """Prints a header line, the current exception and a footer line to shell.
141
+
142
+ This must be called from inside of a try/except statement.
143
+ """
144
+ stream = self.stream
145
+ if stream is None:
146
+ return False
147
+
148
+ print(f" {msg} ".center(width, "-"), file=sys.__stderr__)
149
+ traceback.print_exc(limit=limit, file=stream, chain=chain)
150
+ print(f" {msg} ".center(width, "-"), file=sys.__stderr__)
151
+ return True
152
+
153
+ @property
154
+ def stream(self):
155
+ if self.error:
156
+ return sys.__stderr__
157
+ return sys.__stdout__
158
+
159
+
160
+ class Truncate:
161
+ def __init__(self, text, sep='...'):
162
+ self.text = text
163
+ self.sep = sep
164
+ self.sep_spaces = f' {sep} '
165
+
166
+ def middle(self, n=100):
167
+ """Truncates the provided text to a fixed length, putting the sep in the middle.
168
+ https://www.xormedia.com/string-truncate-middle-with-ellipsis/
169
+ """
170
+ if len(self.text) <= n:
171
+ # string is already short-enough
172
+ return self.text
173
+ # half of the size, minus the seperator
174
+ n_2 = int(n) // 2 - len(self.sep_spaces)
175
+ # whatever's left
176
+ n_1 = n - n_2 - len(self.sep_spaces)
177
+ return '{0}{1}{2}'.format(self.text[:n_1], self.sep_spaces, self.text[-n_2:])
178
+
179
+ def lines(self, max_lines=20):
180
+ """Truncates the provided text to a maximum number of lines with a separator
181
+ at the end if required.
182
+ """
183
+ lines = self.text.split("\n")
184
+ orig_len = len(lines)
185
+ lines = lines[:max_lines]
186
+ trim_len = len(lines)
187
+ if orig_len != trim_len:
188
+ lines.append("...")
189
+ truncated = "\n".join(lines)
190
+
191
+ return truncated
@@ -0,0 +1,86 @@
1
+ import functools
2
+ import inspect
3
+ import logging
4
+ import threading
5
+
6
+ _logger = logging.getLogger(__name__)
7
+
8
+
9
+ class CallStack:
10
+ """Decorator that logs the inputs and return of the decorated function.
11
+
12
+ For most cases use `@preditor.utils.call_stack.log_calls`, but if you want
13
+ to create a custom configured version you can create your own instance of
14
+ `CallStack` to customize the output.
15
+
16
+ Parameters:
17
+ logger: A python logging instance to write log messages to.
18
+ level: Write to logger using this debug level.
19
+ print: If set to True use the print function instead of python logging.
20
+ input_prefix: Text shown before the function name.
21
+ return_prefix: Text shown before the return data.
22
+ """
23
+
24
+ def __init__(self, logger=None, level=logging.DEBUG, print=True):
25
+ self._call_depth = threading.local()
26
+ self.logger = _logger if logger is None else logger
27
+ self.level = level
28
+ self.print = print
29
+ self.input_prefix = "\u2192"
30
+ self.return_prefix = "\u2190"
31
+
32
+ @property
33
+ def indent(self):
34
+ return getattr(self._call_depth, "indent", 0)
35
+
36
+ @indent.setter
37
+ def indent(self, indent):
38
+ self._call_depth.indent = indent
39
+
40
+ def log(self, msg):
41
+ if self.print:
42
+ print(msg)
43
+ else:
44
+ self.logger.log(self.level, msg, stacklevel=2)
45
+
46
+ def log_calls(self, func):
47
+ """Decorator that writes function input and return value.
48
+ If another decorated function call is made during the first call it's
49
+ output will be indented to reflect that.
50
+ """
51
+ # Check for and remove self and cls arguments once during decoration
52
+ sig = inspect.signature(func)
53
+ params = list(sig.parameters.values())
54
+ slice_index = 1 if params and params[0].name in ("self", "cls") else 0
55
+
56
+ @functools.wraps(func)
57
+ def wrapper(*args, **kwargs):
58
+ # remove 'self' or 'cls' from positional display (but do NOT remove kwargs)
59
+ # display_args = args[1:] if slice_index and args else args
60
+ display_args = args[slice_index:]
61
+
62
+ # Generate repr of the calling arguments
63
+ parts = [repr(a) for a in display_args]
64
+ parts += [f"{k}={v!r}" for k, v in kwargs.items()]
65
+ arg_str = ", ".join(parts)
66
+
67
+ indent = " " * self.indent
68
+ self.log(f"{indent}{self.input_prefix} {func.__qualname__}({arg_str})")
69
+
70
+ self.indent += 1
71
+ try:
72
+ result = func(*args, **kwargs)
73
+ finally:
74
+ self.indent -= 1
75
+
76
+ self.log(f"{indent}{self.return_prefix} {result!r}")
77
+ return result
78
+
79
+ return wrapper
80
+
81
+
82
+ call_stack = CallStack()
83
+ """An shared instance for ease of use and configuration"""
84
+
85
+ log_calls = call_stack.log_calls
86
+ """Use `from preditor.utils.call_stack import log_calls` as a shared decorator."""
preditor/utils/cute.py ADDED
@@ -0,0 +1,106 @@
1
+ __all__ = ["ensureWindowIsVisible"]
2
+ from functools import partial
3
+
4
+ # NOTE: Only import QtWidgets and QtGui inside functions not at the module level
5
+ # to preserve headless environment support.
6
+ from Qt.QtCore import Property
7
+
8
+
9
+ def ensureWindowIsVisible(widget):
10
+ """
11
+ Checks the widget's geometry against all of the system's screens. If it does
12
+ not intersect, it will reposition it to the top left corner of the highest
13
+ numbered screen. Returns a boolean indicating if it had to move the widget.
14
+ """
15
+ from Qt.QtWidgets import QApplication
16
+
17
+ screens = QApplication.screens()
18
+ geo = widget.geometry()
19
+
20
+ for screen in screens:
21
+ if screen.geometry().intersects(geo):
22
+ break
23
+ else:
24
+ monGeo = screens[-1].geometry() # Use the last screen available
25
+ geo.moveTo(monGeo.x() + 7, monGeo.y() + 30)
26
+
27
+ # Setting the geometry may trigger a second check if setGeometry is overridden
28
+ disable = hasattr(widget, 'checkScreenGeo') and widget.checkScreenGeo
29
+ if disable:
30
+ widget.checkScreenGeo = False
31
+ widget.setGeometry(geo)
32
+ if disable:
33
+ widget.checkScreenGeo = True
34
+ return True
35
+ return False
36
+
37
+
38
+ def QtPropertyInit(name, default, callback=None, typ=None):
39
+ """Initializes a default Property value with a usable getter and setter.
40
+
41
+ You can optionally pass a function that will get called any time the property
42
+ is set. If using the same callback for multiple properties, you may want to
43
+ use the preditor.decorators.singleShot decorator to prevent your function getting
44
+ called multiple times at once. This callback must accept the attribute name and
45
+ value being set.
46
+
47
+ Example:
48
+ class TestClass(QWidget):
49
+ def __init__(self, *args, **kwargs):
50
+ super(TestClass, self).__init__(*args, **kwargs)
51
+
52
+ stdoutColor = QtPropertyInit('_stdoutColor', QColor(0, 0, 255))
53
+ pyForegroundColor = QtPropertyInit('_pyForegroundColor', QColor(0, 0, 255))
54
+
55
+ Args:
56
+ name(str): The name of internal attribute to store to and lookup from.
57
+ default: The property's default value. This will also define the Property type
58
+ if typ is not set. To define a property containing a list, dict or set,
59
+ pass the list, dict, or set class not an instance of the class. Ie pass
60
+ `list` not `[]`. See flake8-bugbear rule B006 for more info.
61
+ callback(callable): If provided this function is called when the property is
62
+ set.
63
+ typ (class, optional): If not None this value is used to specify the type of
64
+ the Property. This is useful when you need to specify a property as python's
65
+ object but pass a default value of a given class.
66
+
67
+ Returns:
68
+ Property
69
+ """
70
+
71
+ # Prevent all instances of class sharing a mutable data structure. If the
72
+ # default is one of these classes and not a instance of them, replace them
73
+ # with an instance of the default class.
74
+ # See flake8-bugbear B006: Do not use mutable data structures for argument
75
+ # defaults. They are created during function definition time. All calls to
76
+ # the function reuse this one instance of that data structure, persisting
77
+ # changes between them.
78
+ is_mutable = default in (list, dict, set)
79
+
80
+ def _getattrDefault(default, is_mutable, self, attrName):
81
+ try:
82
+ value = getattr(self, attrName)
83
+ except AttributeError:
84
+ # Create a unique instance of the default mutable class for self.
85
+ if is_mutable:
86
+ default = default()
87
+
88
+ setattr(self, attrName, default)
89
+ return default
90
+ return value
91
+
92
+ def _setattrCallback(callback, attrName, self, value):
93
+ setattr(self, attrName, value)
94
+ if callback:
95
+ callback(self, attrName, value)
96
+
97
+ ga = partial(_getattrDefault, default, is_mutable)
98
+ sa = partial(_setattrCallback, callback, name)
99
+ # Use the default value's class if typ is not provided.
100
+ if typ is None:
101
+ if is_mutable:
102
+ # In this case the type is the same as the default
103
+ typ = default
104
+ else:
105
+ typ = default.__class__
106
+ return Property(typ, fget=(lambda s: ga(s, name)), fset=(lambda s, v: sa(s, v)))
@@ -0,0 +1,54 @@
1
+ from __future__ import absolute_import
2
+
3
+ import glob
4
+ import os
5
+
6
+
7
+ def read_stylesheet(stylesheet='', path=None):
8
+ """Returns the contents of the requested stylesheet.
9
+
10
+ Args:
11
+
12
+ stylesheet (str): the name of the stylesheet. Attempt to load stylesheet.css
13
+ shipped with preditor. Ignored if path is provided.
14
+
15
+ path (str): Return the contents of this file path.
16
+
17
+ Returns:
18
+ str: The contents of stylesheet or blank if stylesheet was not found.
19
+ valid: A stylesheet was found and loaded.
20
+ """
21
+ if path is None:
22
+ path = os.path.join(
23
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
24
+ 'resource',
25
+ 'stylesheet',
26
+ '{}.css'.format(stylesheet),
27
+ )
28
+ if os.path.isfile(path):
29
+ with open(path) as f:
30
+ return f.read(), True
31
+ return '', False
32
+
33
+
34
+ def stylesheets(subFolder=None):
35
+ """Returns a list of installed stylesheet names.
36
+
37
+ Args:
38
+ subFolder (str or None, optional): Use this to access sub-folders of
39
+ the stylesheet resource directory.
40
+
41
+ Returns:
42
+ list: A list .css file paths in the target directory.
43
+ """
44
+ components = [
45
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
46
+ 'resource',
47
+ 'stylesheet',
48
+ ]
49
+ if subFolder is not None:
50
+ components.append(subFolder)
51
+ cssdir = os.path.join(*components)
52
+ cssfiles = sorted(glob.glob(os.path.join(cssdir, '*.css')))
53
+ # Only return the filename without the .css extension
54
+ return [os.path.splitext(os.path.basename(fp))[0] for fp in cssfiles]