pyscript-programming-language 1.13.3__py3-none-any.whl → 1.14.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 (41) hide show
  1. pyscript/__init__.py +6 -4
  2. pyscript/__init__.pyi +3 -1
  3. pyscript/__main__.py +99 -53
  4. pyscript/core/cache.py +1 -0
  5. pyscript/core/constants.py +8 -7
  6. pyscript/core/context.py +7 -10
  7. pyscript/core/editor/__init__.py +27 -11
  8. pyscript/core/editor/bases.py +20 -19
  9. pyscript/core/editor/gui.py +23 -22
  10. pyscript/core/editor/terminal.py +5 -5
  11. pyscript/core/exceptions.py +8 -19
  12. pyscript/core/handlers.py +34 -32
  13. pyscript/core/highlight.py +7 -25
  14. pyscript/core/interpreter.py +21 -18
  15. pyscript/core/lexer.py +8 -36
  16. pyscript/core/mapping.py +55 -3
  17. pyscript/core/parser.py +102 -48
  18. pyscript/core/position.py +86 -81
  19. pyscript/core/pysbuiltins.py +55 -61
  20. pyscript/core/pystypes.py +19 -14
  21. pyscript/core/runner.py +10 -9
  22. pyscript/core/shell.py +14 -16
  23. pyscript/core/symtab.py +14 -15
  24. pyscript/core/token.py +3 -4
  25. pyscript/core/utils/ansi.py +70 -45
  26. pyscript/core/utils/debug.py +18 -5
  27. pyscript/core/utils/generic.py +22 -3
  28. pyscript/core/utils/similarity.py +8 -7
  29. pyscript/core/utils/string.py +10 -12
  30. pyscript/core/version.py +5 -14
  31. pyscript/lib/ansi.pys +1 -14
  32. pyscript/lib/brainfuck.pys +3 -6
  33. pyscript/lib/fpstimer/py_fpstimer.py +11 -1
  34. pyscript/lib/history.pys +2 -3
  35. pyscript/lib/parser.pys +9 -3
  36. pyscript/this.py +2 -2
  37. {pyscript_programming_language-1.13.3.dist-info → pyscript_programming_language-1.14.0.dist-info}/METADATA +34 -32
  38. pyscript_programming_language-1.14.0.dist-info/RECORD +79 -0
  39. {pyscript_programming_language-1.13.3.dist-info → pyscript_programming_language-1.14.0.dist-info}/WHEEL +1 -1
  40. pyscript_programming_language-1.13.3.dist-info/RECORD +0 -79
  41. {pyscript_programming_language-1.13.3.dist-info → pyscript_programming_language-1.14.0.dist-info}/top_level.txt +0 -0
pyscript/__init__.py CHANGED
@@ -9,10 +9,10 @@ if __import__('sys').version_info < (3, 10):
9
9
  from . import core
10
10
 
11
11
  from .core.constants import (
12
- DEFAULT, NO_COLOR, DEBUG, SILENT, RETURN_RESULT, DONT_SHOW_BANNER_ON_SHELL, CLASSIC_LINE_SHELL, NO_COLOR_PROMPT,
13
- NOTEBOOK, LEXER_HIGHLIGHT, DICT_TO_JSDICT
12
+ DEFAULT, NO_COLOR, NO_WARNING, SILENT, DEBUG, RETURN_RESULT, DONT_SHOW_BANNER_ON_SHELL, CLASSIC_LINE_SHELL,
13
+ NO_COLOR_PROMPT, NOTEBOOK, LEXER_HIGHLIGHT, DICT_TO_JSDICT
14
14
  )
15
- from .core.cache import undefined
15
+ from .core.cache import pys_sys, undefined
16
16
  from .core.highlight import (
17
17
  HLFMT_HTML, HLFMT_ANSI, HLFMT_BBCODE, pys_highlight, PygmentsPyScriptStyle, PygmentsPyScriptLexer,
18
18
  PygmentsPyScriptShellLexer
@@ -24,8 +24,9 @@ __all__ = (
24
24
  'core',
25
25
  'DEFAULT',
26
26
  'NO_COLOR',
27
- 'DEBUG',
27
+ 'NO_WARNING',
28
28
  'SILENT',
29
+ 'DEBUG',
29
30
  'RETURN_RESULT',
30
31
  'DONT_SHOW_BANNER_ON_SHELL',
31
32
  'CLASSIC_LINE_SHELL',
@@ -37,6 +38,7 @@ __all__ = (
37
38
  'HLFMT_ANSI',
38
39
  'HLFMT_BBCODE',
39
40
  'undefined',
41
+ 'pys_sys',
40
42
  'version',
41
43
  'version_info',
42
44
  'pys_highlight',
pyscript/__init__.pyi CHANGED
@@ -22,8 +22,9 @@ from .core.highlight import (
22
22
 
23
23
  DEFAULT: int
24
24
  NO_COLOR: int
25
- DEBUG: int
25
+ NO_WARNING: int
26
26
  SILENT: int
27
+ DEBUG: int
27
28
  RETURN_RESULT: int
28
29
  DONT_SHOW_BANNER_ON_SHELL: int
29
30
  CLASSIC_LINE_SHELL: int
@@ -36,6 +37,7 @@ HLFMT_HTML: PysHighlightFormatter
36
37
  HLFMT_ANSI: PysHighlightFormatter
37
38
  HLFMT_BBCODE: PysHighlightFormatter
38
39
 
40
+ pys_sys: ModuleType
39
41
  undefined: PysUndefined
40
42
  version: str
41
43
  version_info: PysVersionInfo
pyscript/__main__.py CHANGED
@@ -1,11 +1,10 @@
1
1
  from .core.buffer import PysFileBuffer
2
2
  from .core.cache import pys_sys, undefined
3
3
  from .core.constants import (
4
- ENV_PYSCRIPT_NO_COLOR_PROMPT, ENV_PYSCRIPT_CLASSIC_LINE_SHELL, DEFAULT, DEBUG, NO_COLOR, DONT_SHOW_BANNER_ON_SHELL,
5
- CLASSIC_LINE_SHELL, NO_COLOR_PROMPT, NOTEBOOK
4
+ ENV_PYSCRIPT_NO_COLOR_PROMPT, ENV_PYSCRIPT_CLASSIC_LINE_SHELL, DEFAULT, NO_COLOR, NO_WARNING, DEBUG,
5
+ DONT_SHOW_BANNER_ON_SHELL, CLASSIC_LINE_SHELL, NO_COLOR_PROMPT, NOTEBOOK
6
6
  )
7
- from .core.editor.gui import PysGUIEditor, GUI_SUPPORT
8
- from .core.editor.terminal import PysTerminalEditor, TERMINAL_SUPPORT
7
+ from .core import editor
9
8
  from .core.highlight import (
10
9
  PYGMENTS, HLFMT_HTML, HLFMT_ANSI, HLFMT_BBCODE, pys_highlight, PygmentsPyScriptStyle, PygmentsPyScriptLexer
11
10
  )
@@ -33,24 +32,29 @@ if PYGMENTS:
33
32
  }
34
33
 
35
34
  from argparse import OPTIONAL, REMAINDER, ArgumentParser
35
+ from re import compile as compile_regex
36
36
 
37
37
  import sys
38
38
 
39
+ if sys.version_info >= (3, 13):
40
+ try:
41
+ from _colorize import can_colorize
42
+ COLORIZE = can_colorize()
43
+ except:
44
+ # respect for NO_COLOR, see more here: https://no-color.org
45
+ COLORIZE = not is_environ('NO_COLOR')
46
+ else:
47
+ COLORIZE = True
48
+
39
49
  FORMATER_HIGHLIGHT_MAP = {
40
50
  'html': HLFMT_HTML,
41
51
  'ansi': HLFMT_ANSI,
42
52
  'bbcode': HLFMT_BBCODE
43
53
  }
44
54
 
45
- EDITOR_MAP = {
46
- name: cls
47
- for support, name, cls in [
48
- (GUI_SUPPORT, 'gui', PysGUIEditor),
49
- (TERMINAL_SUPPORT, 'terminal', PysTerminalEditor)
50
- ] if support
51
- }
52
-
53
- arguments_requiring_value = {'-l', '--highlight', '-r', '--py-recursion'}
55
+ editors = tuple(editor for editor in editor.__all__ if editor != 'bases')
56
+ arguments_requiring_argv = {'-c', '--command', '-m', '--module'}
57
+ arguments_requiring_value = {'-e', '--editor', '-l', '--highlight', '-r', '--py-recursion'}
54
58
 
55
59
  parser = ArgumentParser(
56
60
  prog=f'{base(sys.executable)} -m pyscript',
@@ -77,14 +81,12 @@ parser.add_argument(
77
81
  help="set a debug flag, this will ignore assert statement. Check the flag is active with the __debug__ keyword"
78
82
  )
79
83
 
80
- if EDITOR_MAP:
81
- parser.add_argument(
82
- '-e', '--editor',
83
- choices=tuple(EDITOR_MAP.keys()),
84
- default=None,
85
- help="open the editor panel from a 'file'",
86
- )
87
- arguments_requiring_value.update({'-e', '--editor'})
84
+ parser.add_argument(
85
+ '-e', '--editor',
86
+ choices=editors,
87
+ default=None,
88
+ help="open the editor panel from a 'file'",
89
+ )
88
90
 
89
91
  parser.add_argument(
90
92
  '-i', '--inspect',
@@ -100,7 +102,7 @@ parser.add_argument(
100
102
 
101
103
  parser.add_argument(
102
104
  '-l', '--highlight',
103
- choices=tuple(FORMATER_HIGHLIGHT_MAP.keys()) + tuple(FORMATER_PYGMENTS_MAP.keys() if PYGMENTS else ()),
105
+ choices=tuple(FORMATER_HIGHLIGHT_MAP.keys()) + (tuple(FORMATER_PYGMENTS_MAP.keys()) if PYGMENTS else ()),
104
106
  default=None,
105
107
  help="generate highlight code from a 'file'"
106
108
  )
@@ -139,6 +141,12 @@ parser.add_argument(
139
141
  help="configure terminal encoding to UTF-8 and enable ANSI escape code processing on Windows"
140
142
  )
141
143
 
144
+ parser.add_argument(
145
+ '-w', '--no-warning',
146
+ action='store_true',
147
+ help="disabled warning (especially syntax warnings)"
148
+ )
149
+
142
150
  parser.add_argument(
143
151
  '-q',
144
152
  action='store_true',
@@ -179,23 +187,27 @@ argv = sys.argv[1:]
179
187
  argc = len(argv)
180
188
  index = 0
181
189
  arg_index = -1
190
+ parser_color = COLORIZE
182
191
 
183
192
  while index < argc:
184
193
  arg = argv[index]
185
- if not arg.startswith('-') or arg == '--':
186
- break
187
194
 
188
- if arg in ('-c', '--command'):
189
- arg_index = index + 2
195
+ if not arg.startswith('-') or arg == '--':
190
196
  break
191
- elif arg in ('-m', '--module'):
197
+ elif arg in arguments_requiring_argv:
192
198
  arg_index = index + 2
193
199
  break
200
+ elif arg in ('-n', '--no-color'):
201
+ parser_color = False
194
202
 
195
203
  index += 1
196
204
  if arg in arguments_requiring_value:
197
205
  index += 1
198
206
 
207
+ if sys.version_info >= (3, 14):
208
+ parser.color = parser_color
209
+ parser.suggest_on_error = True
210
+
199
211
  args = parser.parse_args(argv if arg_index == -1 else argv[:arg_index])
200
212
  arg = args.arg if arg_index == -1 else argv[arg_index:]
201
213
 
@@ -220,7 +232,7 @@ if args.terminal:
220
232
  pass
221
233
 
222
234
  if args.file is None:
223
- if EDITOR_MAP and args.editor:
235
+ if args.editor:
224
236
  argument_error('-e/--editor', "argument 'file' is required")
225
237
  elif args.highlight:
226
238
  argument_error('-l/--highlight', "argument 'file' is required")
@@ -231,66 +243,97 @@ if args.py_recursion is not None:
231
243
  except BaseException as e:
232
244
  argument_error('-r/--py-recursion', e)
233
245
 
246
+ if args.P:
247
+ for cwd in {'', '.', getcwd()}:
248
+ remove_python_path(cwd)
249
+
234
250
  code = 0
235
251
  flags = DEFAULT
236
252
 
237
253
  for condition, flag in [
238
254
  (args.notebook or USE_NOTEBOOK, NOTEBOOK),
239
255
  (args.classic_line_shell or is_environ(ENV_PYSCRIPT_CLASSIC_LINE_SHELL), CLASSIC_LINE_SHELL),
240
- (args.no_color or is_environ('NO_COLOR'), NO_COLOR),
256
+ (args.no_color or not COLORIZE, NO_COLOR),
241
257
  (args.no_color_prompt or is_environ(ENV_PYSCRIPT_NO_COLOR_PROMPT), NO_COLOR_PROMPT),
242
258
  (args.debug, DEBUG),
259
+ (args.no_warning, NO_WARNING),
243
260
  (args.q, DONT_SHOW_BANNER_ON_SHELL)
244
261
  ]:
245
262
  if condition:
246
263
  flags |= flag
247
264
 
248
- if args.P:
249
- for cwd in {'', '.', getcwd()}:
250
- remove_python_path(cwd)
251
-
252
265
  pys_sys.argv = argv = ['', *arg]
253
266
 
254
267
  def clean_up() -> None:
255
268
  g = globals()
256
269
 
257
270
  for name in {
258
- 'ArgumentParser', 'BBCodeFormatter', 'CLASSIC_LINE_SHELL', 'DEBUG', 'DEFAULT', 'EDITOR_MAP',
259
- 'ENV_PYSCRIPT_CLASSIC_LINE_SHELL', 'ENV_PYSCRIPT_NO_COLOR_PROMPT', 'FORMATER_HIGHLIGHT_MAP',
260
- 'FORMATER_PYGMENTS_MAP', 'GUI_SUPPORT', 'HLFMT_ANSI', 'HLFMT_BBCODE', 'HLFMT_HTML', 'HtmlFormatter',
261
- 'LatexFormatter', 'NOTEBOOK', 'NO_COLOR', 'NO_COLOR_PROMPT', 'OPTIONAL', 'PYGMENTS', 'PygmentsPyScriptLexer',
262
- 'PygmentsPyScriptStyle', 'PysFileBuffer', 'PysGUIEditor', 'PysTerminalEditor', 'REMAINDER', 'TERMINAL_SUPPORT',
263
- 'Terminal256Formatter', 'TerminalFormatter', 'TerminalTrueColorFormatter', 'USE_NOTEBOOK', '__version__',
264
- '_namespace_to_symbol_table', 'arg', 'arg_index', 'argc', 'args', 'argument_error', 'arguments_requiring_value',
265
- 'argv', 'base', 'clean_up', 'condition', 'ctypes', 'execute', 'fd', 'file', 'find_module_path', 'flag',
266
- 'getcwd', 'highlight', 'i', 'index', 'is_environ', 'kernel32', 'load_file', 'module_path', 'parser',
267
- 'pys_highlight', 'pys_sys', 'remove_python_path'
271
+ 'ArgumentParser', 'BBCodeFormatter', 'CLASSIC_LINE_SHELL', 'DEBUG', 'DEFAULT', 'i', 'index', 'is_environ', 'fd',
272
+ 'kernel32', 'ENV_PYSCRIPT_CLASSIC_LINE_SHELL', 'ENV_PYSCRIPT_NO_COLOR_PROMPT', 'FORMATER_HIGHLIGHT_MAP', 'arg',
273
+ 'FORMATER_PYGMENTS_MAP', 'HLFMT_ANSI', 'HLFMT_BBCODE', 'HLFMT_HTML', 'HtmlFormatter', 'OPTIONAL', 'PYGMENTS',
274
+ 'LatexFormatter', 'NOTEBOOK', 'NO_COLOR', 'NO_WARNING', 'NO_COLOR_PROMPT', 'PygmentsPyScriptLexer', 'args',
275
+ 'PygmentsPyScriptStyle', 'PysFileBuffer', 'REMAINDER', 'Terminal256Formatter', 'TerminalFormatter', 'flag',
276
+ 'TerminalTrueColorFormatter', 'USE_NOTEBOOK', '__version__', '_namespace_to_symbol_table', 'arg_index', 'argc',
277
+ 'argument_error', 'arguments_requiring_value', 'argv', 'base', 'clean_up', 'condition', 'ctypes', 'execute',
278
+ 'file', 'find_module_path', 'getcwd', 'highlight', 'load_editor_module', 'load_file', 'module_path', 'parser',
279
+ 'pys_highlight', 'pys_sys', 'remove_python_path', 'editors', 'editor', 'compile_regex', 'can_colorize',
280
+ 'arguments_requiring_argv', 'COLORIZE', 'parser_color'
268
281
  }:
269
282
  try:
270
283
  del g[name]
271
284
  except:
272
285
  continue
273
286
 
287
+ def load_editor_module(name: str) -> tuple[type | None, bool]:
288
+ if name not in editors:
289
+ return None, False
290
+
291
+ module = getattr(editor, name, None)
292
+ if module is None:
293
+ return None, False
294
+
295
+ class_name = compile_regex(r'Pys[A-Za-z0-9_]+Editor').fullmatch
296
+ support_name = compile_regex(r'[A-Z0-9_]+_SUPPORT').fullmatch
297
+
298
+ cls = None
299
+ support = None
300
+ for name in dir(module):
301
+ if not (cls is None or support is None):
302
+ break
303
+ elif class_name(name) and cls is None:
304
+ cls = getattr(module, name)
305
+ elif support_name(name) and support is None:
306
+ support = getattr(module, name)
307
+ else:
308
+ return None, False
309
+
310
+ return cls, support
311
+
274
312
  def load_file(path) -> PysFileBuffer:
275
313
  normalized = normpath(path)
314
+
315
+ def open_error(message: str) -> None:
316
+ argument_error('file', f"can't open file \"{normalized}\": {message}")
317
+
276
318
  try:
277
- with open(normalized, 'r', encoding='utf-8') as file:
319
+ with open(normalized, 'r', encoding=pys_sys.encoding) as file:
278
320
  return PysFileBuffer(file, normalized)
279
321
  except FileNotFoundError:
280
- if not (EDITOR_MAP and args.editor):
281
- argument_error('file', f"can't open file \"{normalized}\": No such file or directory")
322
+ if not args.editor:
323
+ open_error("No such file or directory")
282
324
  except PermissionError:
283
- argument_error('file', f"can't open file \"{normalized}\": Permission denied")
325
+ open_error("Permission denied")
284
326
  except IsADirectoryError:
285
- argument_error('file', f"can't open file \"{normalized}\": Path is not a file")
327
+ open_error("Path is not a file")
286
328
  except NotADirectoryError:
287
- argument_error('file', f"can't open file \"{normalized}\": Attempting to access directory from file")
329
+ open_error("Attempting to access directory from file")
288
330
  except (OSError, IOError):
289
- argument_error('file', f"can't open file \"{normalized}\": Attempting to access a system directory or file")
331
+ open_error("Attempting to access a system directory or file")
290
332
  except UnicodeDecodeError:
291
- argument_error('file', f"can't read file \"{normalized}\": Bad file")
333
+ open_error("Bad file")
292
334
  except BaseException as e:
293
335
  argument_error('file', f"file \"{normalized}\": Unexpected error: {e}")
336
+
294
337
  return PysFileBuffer('', path)
295
338
 
296
339
  def execute(file: PysFileBuffer) -> None:
@@ -327,9 +370,12 @@ if args.file is not None:
327
370
  argv[0] = args.file
328
371
  file = load_file(args.file)
329
372
 
330
- if EDITOR_MAP and args.editor:
373
+ if args.editor:
374
+ cls, supported = load_editor_module(args.editor)
375
+ if not supported:
376
+ argument_error('-e/--editor', f"'{args.editor}' mode not supported")
331
377
  try:
332
- EDITOR_MAP[args.editor](file=file, colored=not (flags & NO_COLOR)).run()
378
+ cls(file, colored=not (flags & NO_COLOR)).run()
333
379
  except BaseException as e:
334
380
  argument_error('-e/--editor', e)
335
381
 
pyscript/core/cache.py CHANGED
@@ -31,6 +31,7 @@ pys_sys.modules = {}
31
31
  pys_sys.interns = {}
32
32
  pys_sys.singletons = {}
33
33
  pys_sys.argv = ['']
34
+ pys_sys.encoding = 'utf-8'
34
35
  pys_sys.flags = DEFAULT
35
36
  pys_sys.displayhook = print_display
36
37
  pys_sys.excepthook = print_traceback
@@ -10,7 +10,7 @@ GLOBAL_HISTORY_PATH = os.path.join(os.path.expanduser('~'), '.pyscript_history')
10
10
  CONFIGURATIONS_PATH = os.path.join(OTHER_PATH, 'configurations.json')
11
11
 
12
12
  ENV_PYSCRIPT_NO_EXCEPTHOOK = 'PYSCRIPT_NO_EXCEPTHOOK'
13
- ENV_PYSCRIPT_NO_GIL = 'PYSCRIPT_NO_GIL'
13
+ ENV_PYSCRIPT_WITH_GIL = 'PYSCRIPT_WITH_GIL'
14
14
  ENV_PYSCRIPT_NO_READLINE = 'PYSCRIPT_NO_READLINE'
15
15
  ENV_PYSCRIPT_NO_TYPECHECK = 'PYSCRIPT_NO_TYPECHECK'
16
16
  ENV_PYSCRIPT_NO_COLOR_PROMPT = 'PYSCRIPT_NO_COLOR_PROMPT'
@@ -34,12 +34,13 @@ CONSTANT_KEYWORDS = (
34
34
 
35
35
  DEFAULT = 0
36
36
  NO_COLOR = 1 << 0
37
- DEBUG = 1 << 1
38
- SILENT = 1 << 2
39
- RETURN_RESULT = 1 << 3
40
- DONT_SHOW_BANNER_ON_SHELL = 1 << 4
41
- CLASSIC_LINE_SHELL = 1 << 5
42
- NO_COLOR_PROMPT = 1 << 6
37
+ NO_WARNING = 1 << 1
38
+ SILENT = 1 << 2 | NO_WARNING
39
+ DEBUG = 1 << 3
40
+ RETURN_RESULT = 1 << 4
41
+ DONT_SHOW_BANNER_ON_SHELL = 1 << 5
42
+ CLASSIC_LINE_SHELL = 1 << 6
43
+ NO_COLOR_PROMPT = 1 << 7
43
44
  NOTEBOOK = CLASSIC_LINE_SHELL | NO_COLOR_PROMPT
44
45
 
45
46
  LEXER_HIGHLIGHT = 1 << 0
pyscript/core/context.py CHANGED
@@ -3,12 +3,9 @@ from .buffer import PysFileBuffer
3
3
  from .constants import DEFAULT
4
4
  from .position import PysPosition
5
5
  from .symtab import PysSymbolTable, PysClassSymbolTable
6
- from .utils.decorators import immutable
7
- from .utils.generic import setimuattr
8
6
 
9
7
  from typing import Optional
10
8
 
11
- @immutable
12
9
  class PysContext(Pys):
13
10
 
14
11
  __slots__ = ('file', 'name', 'qualname', 'flags', 'symbol_table', 'parent', 'parent_entry_position')
@@ -28,13 +25,13 @@ class PysContext(Pys):
28
25
  if flags is None and parent:
29
26
  flags = parent.flags
30
27
 
31
- setimuattr(self, 'file', file)
32
- setimuattr(self, 'name', name)
33
- setimuattr(self, 'qualname', qualname)
34
- setimuattr(self, 'flags', DEFAULT if flags is None else flags)
35
- setimuattr(self, 'symbol_table', symbol_table)
36
- setimuattr(self, 'parent', parent)
37
- setimuattr(self, 'parent_entry_position', parent_entry_position)
28
+ self.file = file
29
+ self.name = name
30
+ self.qualname = qualname
31
+ self.flags = DEFAULT if flags is None else flags
32
+ self.symbol_table = symbol_table
33
+ self.parent = parent
34
+ self.parent_entry_position = parent_entry_position
38
35
 
39
36
  class PysClassContext(PysContext):
40
37
 
@@ -1,15 +1,31 @@
1
1
  """
2
2
  All Editor Application Implementations for PyScript.
3
+
4
+ Not all editors are loaded (lazy import).
3
5
  """
4
6
 
5
- from . import (
6
- bases,
7
- gui,
8
- terminal
9
- )
10
-
11
- __all__ = (
12
- 'bases',
13
- 'gui',
14
- 'terminal'
15
- )
7
+ from ..utils.path import normpath
8
+
9
+ from importlib import import_module as _import
10
+ from os.path import dirname
11
+ from pkgutil import iter_modules
12
+
13
+ __all__ = tuple(frozenset(
14
+ name for _, name, _ in
15
+ iter_modules((dirname(normpath(__file__)),))
16
+ ))
17
+
18
+ def __getattr__(name: str):
19
+ g = globals()
20
+ if name in g:
21
+ return g[name]
22
+ elif name in __all__:
23
+ module = _import(f'{__name__}.{name}')
24
+ g[name] = module
25
+ return module
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
27
+
28
+ def __dir__() -> list[str]:
29
+ return sorted(tuple(globals().keys()) + __all__)
30
+
31
+ del normpath, iter_modules, dirname
@@ -1,64 +1,64 @@
1
1
  from ..bases import Pys
2
2
  from ..buffer import PysFileBuffer
3
+ from ..cache import pys_sys
3
4
  from ..constants import CONFIGURATIONS_PATH
4
5
  from ..utils.decorators import typecheck, inheritable
5
- from ..utils.generic import setimuattr
6
+ from ..utils.generic import delimuattr
6
7
  from ..utils.string import normstr
7
8
 
8
9
  from json import dump, load
10
+ from os.path import basename
9
11
  from typing import Any
10
12
 
11
- import os
12
-
13
13
  class PysEditor(Pys):
14
14
 
15
15
  @typecheck
16
16
  def __init__(self, file: PysFileBuffer, colored: bool = True) -> None:
17
17
  self.file = file
18
18
  self.colored = bool(colored)
19
- self.basename = os.path.basename(self.file.name)
19
+ self.basename = basename(self.file.name)
20
20
  self.used = False
21
21
  self.modified = False
22
22
 
23
+ self.load_configuration()
24
+
23
25
  def __init_subclass__(cls, **kwargs) -> None:
24
26
  super().__init_subclass__(**kwargs)
25
27
  inheritable(cls)
26
28
 
27
29
  def load_configuration(self) -> None:
28
30
  try:
29
- with open(CONFIGURATIONS_PATH, 'r', encoding='utf-8') as file:
31
+ with open(CONFIGURATIONS_PATH, 'r', encoding=pys_sys.encoding) as file:
30
32
  result = load(file)
31
- if not isinstance(result, dict):
33
+ if not isinstance(result, dict):
34
+ raise ValueError
35
+ for key in result:
36
+ if not isinstance(key, str):
32
37
  raise ValueError
33
- for key in result:
34
- if not isinstance(key, str):
35
- raise ValueError
36
- self.configurations = result
38
+ self.configurations = result
37
39
  except:
38
40
  self.configurations = {}
39
41
 
40
42
  def get_configuration(self, configuration: str, default: Any) -> Any:
41
- if configuration in self.configurations:
42
- return self.configurations[configuration]
43
- self.configurations[configuration] = default
44
- return default
43
+ return self.configurations.setdefault(configuration, default)
45
44
 
46
45
  def set_configuration(self, configuration: str, value: Any) -> None:
47
46
  self.configurations[configuration] = value
48
47
 
49
48
  def save_configuration(self) -> None:
50
49
  try:
51
- with open(CONFIGURATIONS_PATH, 'w', encoding='utf-8') as file:
50
+ with open(CONFIGURATIONS_PATH, 'w', encoding=pys_sys.encoding) as file:
52
51
  dump(self.configurations, file, separators=(',', ':'))
53
52
  except:
54
53
  pass
55
54
 
56
55
  def save(self, text) -> None:
56
+ if not self.modified:
57
+ return
58
+
57
59
  try:
58
- text = normstr(text)
59
- with open(self.file.name, 'w', encoding='utf-8') as file:
60
- file.write(text)
61
- setimuattr(self.file, 'text', text)
60
+ with open(self.file.name, 'w', encoding=pys_sys.encoding) as file:
61
+ file.write(normstr(text))
62
62
  except:
63
63
  pass
64
64
  else:
@@ -67,4 +67,5 @@ class PysEditor(Pys):
67
67
  def run(self) -> None:
68
68
  if self.used:
69
69
  raise RuntimeError("one application object can only be used once")
70
+ delimuattr(self.file, 'text')
70
71
  self.used = True
@@ -1,7 +1,7 @@
1
1
  from .bases import PysEditor
2
2
  from ..buffer import PysFileBuffer
3
3
  from ..constants import ICON_PATH
4
- from ..highlight import PygmentsPyScriptStyle, PygmentsPyScriptLexer
4
+ from ..highlight import PYGMENTS, PygmentsPyScriptStyle, PygmentsPyScriptLexer
5
5
  from ..utils.generic import boundary
6
6
  from ..version import __version__
7
7
 
@@ -15,7 +15,8 @@ try:
15
15
  PysEditor.__init__(self, file, colored)
16
16
  Tk.__init__(self)
17
17
 
18
- self.load_configuration()
18
+ def update_tab_size():
19
+ self.text.configure(tabs=(self.font.measure(' '),))
19
20
 
20
21
  def on_save(event=None):
21
22
  text = self.text.get('1.0', 'end')
@@ -26,46 +27,40 @@ try:
26
27
  def on_close():
27
28
  if self.modified:
28
29
  answer = messagebox.askyesnocancel('Unsaved changes', 'File has been modified. Save before exit?')
29
- if answer is True:
30
+ if answer is None:
31
+ return
32
+ elif answer:
30
33
  on_save()
31
- self.save_configuration()
32
- self.destroy()
33
- elif answer is False:
34
- self.save_configuration()
35
- self.destroy()
36
- else:
37
- self.save_configuration()
38
- self.destroy()
34
+ self.save_configuration()
35
+ self.destroy()
39
36
 
40
37
  def on_configure(event):
41
38
  if event.widget == self:
42
39
  zoom = self.wm_state() == 'zoomed'
43
40
  self.set_configuration('zoom', zoom)
44
41
  if not zoom:
45
- self.set_configuration(
46
- 'gui-geometry',
47
- f'{event.width}x{event.height}+{event.x}+{event.y}'
48
- )
42
+ self.set_configuration('gui-geometry', f'{event.width}x{event.height}+{event.x}+{event.y}')
49
43
 
50
44
  def on_modified(event=None):
51
- text = self.text
52
- if text.edit_modified():
45
+ if self.text.edit_modified():
53
46
  self.modified = True
54
47
  update()
55
- text.edit_modified(False)
48
+ self.text.edit_modified(False)
56
49
 
57
50
  def on_change_font(value):
58
51
  def wrapper(event=None):
59
52
  font = self.font
60
53
  size = boundary(font.cget('size') + value, 1, 127)
61
54
  font.config(size=size)
55
+ update_tab_size()
62
56
  self.set_configuration('gui-size-font', size)
63
57
  return 'break'
64
58
  return wrapper
65
59
 
66
60
  def on_toggle_wrap(event=None):
67
- wrap = self.text.cget('wrap') != 'char'
68
- self.text.configure(wrap='char' if wrap else 'none')
61
+ text = self.text
62
+ wrap = text.cget('wrap') != 'char'
63
+ text.configure(wrap='char' if wrap else 'none')
69
64
  self.set_configuration('wrap', wrap)
70
65
  return 'break'
71
66
 
@@ -83,8 +78,12 @@ try:
83
78
 
84
79
  def on_enter(event=None):
85
80
  text = self.text
81
+ if text.tag_ranges('sel'):
82
+ text.delete('sel.first', 'sel.last')
83
+
86
84
  line = text.get('insert linestart', 'insert lineend')
87
85
  text.insert('insert', '\n' + line[:len(line) - len(line.lstrip())])
86
+ text.see('insert')
88
87
  return 'break'
89
88
 
90
89
  self.lexer = PygmentsPyScriptLexer()
@@ -102,6 +101,8 @@ try:
102
101
  yscrollcommand=self.scrollbar.set
103
102
  )
104
103
 
104
+ update_tab_size()
105
+
105
106
  self.scrollbar.pack_configure(side='right', fill='y')
106
107
  self.scrollbar.configure(command=self.text.yview)
107
108
 
@@ -140,10 +141,10 @@ try:
140
141
  )
141
142
  )
142
143
 
143
- self.wm_minsize(300, 250)
144
+ self.wm_minsize(250, 200)
144
145
  self.wm_protocol('WM_DELETE_WINDOW', on_close)
145
146
 
146
- if colored:
147
+ if PYGMENTS and colored:
147
148
  tag_configure = self.text.tag_configure
148
149
  for token, style in PygmentsPyScriptStyle.list_styles():
149
150
  color = style['color']