neoprint 0.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.
- neoprint/__init__.py +33 -0
- neoprint/config.py +135 -0
- neoprint/console.py +81 -0
- neoprint/control.py +23 -0
- neoprint/debugger.py +18 -0
- neoprint/format.py +224 -0
- neoprint/frame_info.py +100 -0
- neoprint/markup.py +283 -0
- neoprint/path_scope.py +16 -0
- neoprint/progress.py +177 -0
- neoprint/render.py +121 -0
- neoprint/scope/__init__.py +3 -0
- neoprint/scope/counter.py +24 -0
- neoprint/scope/scope.py +32 -0
- neoprint/scope/timer.py +35 -0
- neoprint/show.py +49 -0
- neoprint/sourcemap.py +72 -0
- neoprint/text_object.py +691 -0
- neoprint-0.1.0.dist-info/METADATA +9 -0
- neoprint-0.1.0.dist-info/RECORD +21 -0
- neoprint-0.1.0.dist-info/WHEEL +4 -0
neoprint/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from . import text_object
|
|
2
|
+
from .config import config
|
|
3
|
+
from .console import bprint
|
|
4
|
+
from .console import console
|
|
5
|
+
from .control import setup
|
|
6
|
+
from .debugger import debugger
|
|
7
|
+
from .format import format
|
|
8
|
+
from .format import format_list
|
|
9
|
+
from .frame_info import FrameInfo
|
|
10
|
+
from .progress import Progress
|
|
11
|
+
from .progress import ProgressItem
|
|
12
|
+
from .progress import Spinner
|
|
13
|
+
from .progress import progress
|
|
14
|
+
from .progress import spinner
|
|
15
|
+
from .scope import scope
|
|
16
|
+
from .show import debug
|
|
17
|
+
from .show import divider
|
|
18
|
+
from .show import error
|
|
19
|
+
from .show import exception
|
|
20
|
+
from .show import expand
|
|
21
|
+
from .show import expand2
|
|
22
|
+
from .show import index
|
|
23
|
+
from .show import info
|
|
24
|
+
from .show import show
|
|
25
|
+
from .show import show as print
|
|
26
|
+
from .show import success
|
|
27
|
+
from .show import vshow
|
|
28
|
+
from .show import warning
|
|
29
|
+
from .text_object import Markdown
|
|
30
|
+
from .text_object import RichObject
|
|
31
|
+
from .text_object import Text
|
|
32
|
+
|
|
33
|
+
__version__ = '0.1.0'
|
neoprint/config.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import typing as tp
|
|
4
|
+
from sys import excepthook as _default_excepthook
|
|
5
|
+
|
|
6
|
+
from rich.traceback import Traceback
|
|
7
|
+
|
|
8
|
+
from .console import console
|
|
9
|
+
from .console import rich_console
|
|
10
|
+
from .debugger import debugger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Config:
|
|
14
|
+
clear_unfinished_stream: bool
|
|
15
|
+
console_width: int
|
|
16
|
+
debug_output: bool
|
|
17
|
+
disable_varnames: bool
|
|
18
|
+
# if source code is obfuscated, disable parsing varnames.
|
|
19
|
+
# this is usually used with `pyportable-crypto` library.
|
|
20
|
+
legacy_windows: bool
|
|
21
|
+
# if true, decrease console color effect.
|
|
22
|
+
multiline_indent: int
|
|
23
|
+
path_style: tp.Literal['filename', 'relpath'] = 'filename'
|
|
24
|
+
# 'relpath' (default): show relative path.
|
|
25
|
+
# for external libraries, will show `[lib_name]/relpath:lineno`
|
|
26
|
+
# 'filename': show only filename.
|
|
27
|
+
# for external libraries, will show `[lib_name]/filename:lineno`
|
|
28
|
+
rich_traceback: bool
|
|
29
|
+
show_funcname: bool
|
|
30
|
+
show_source: bool
|
|
31
|
+
# attach source file path and line number info prefixed to the log -
|
|
32
|
+
# messages.
|
|
33
|
+
# True example:
|
|
34
|
+
# 'main.py:10 >> hello world'
|
|
35
|
+
# False example:
|
|
36
|
+
# 'hello world'
|
|
37
|
+
show_traceback_locals: bool
|
|
38
|
+
show_varnames: bool
|
|
39
|
+
# show both variable names and values. (magic reflection)
|
|
40
|
+
# example:
|
|
41
|
+
# a, b = 1, 2
|
|
42
|
+
# logger.log(a, b, a + b)
|
|
43
|
+
# # enabled: 'main.py:11 >> a = 1; b = 2; a + b = 3'
|
|
44
|
+
# # disabled: 'main.py:11 >> 1, 2, 3'
|
|
45
|
+
show_verbosity_tag: bool
|
|
46
|
+
# example: print(':v8', 'some error happens')
|
|
47
|
+
# enabled: (red text) '[ERROR] some error happens'
|
|
48
|
+
# disabled: (red text) 'some error happens'
|
|
49
|
+
sourcemap_alignment: tp.Literal['left', 'right'] = 'left'
|
|
50
|
+
subthreaded: bool
|
|
51
|
+
# run lk logger in separate thread.
|
|
52
|
+
|
|
53
|
+
_preset_conf: tp.Dict[str, tp.Union[bool, int, str]] = {
|
|
54
|
+
'clear_unfinished_stream': False,
|
|
55
|
+
'console_width': console.width,
|
|
56
|
+
'debug_output': False,
|
|
57
|
+
'disable_varnames': os.getenv('NEOPRINT_DISABLE_VARNAMES') == '1',
|
|
58
|
+
'legacy_windows': os.getenv('NEOPRINT_LEGACY_WINDOWS') == '1',
|
|
59
|
+
'multiline_indent': 2,
|
|
60
|
+
'path_style': 'relpath',
|
|
61
|
+
'rich_traceback': True,
|
|
62
|
+
'show_funcname': False,
|
|
63
|
+
'show_source': True,
|
|
64
|
+
'show_traceback_locals': False,
|
|
65
|
+
'show_varnames': False,
|
|
66
|
+
'show_verbosity_tag': False,
|
|
67
|
+
'sourcemap_alignment': 'left',
|
|
68
|
+
'subthreaded': False,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
for k, v in self._preset_conf.items():
|
|
73
|
+
setattr(self, k, v)
|
|
74
|
+
assert self.rich_traceback
|
|
75
|
+
sys.excepthook = self._custom_excepthook
|
|
76
|
+
|
|
77
|
+
def __call__(self, **kwargs) -> None:
|
|
78
|
+
for k, v in kwargs.items():
|
|
79
|
+
assert hasattr(self, k), k
|
|
80
|
+
self._apply(k, v)
|
|
81
|
+
|
|
82
|
+
def reset(self) -> None:
|
|
83
|
+
for k, v in self._preset_conf.items():
|
|
84
|
+
if getattr(self, k, None) != v: # if None type, always skip it.
|
|
85
|
+
# assert v is not None
|
|
86
|
+
self._apply(k, v)
|
|
87
|
+
|
|
88
|
+
def _apply(self, key: str, val: tp.Union[bool, int, str]) -> None:
|
|
89
|
+
setattr(self, key, val)
|
|
90
|
+
if key == 'console_width':
|
|
91
|
+
assert isinstance(val, int)
|
|
92
|
+
console.width = val
|
|
93
|
+
elif key == 'debug_output':
|
|
94
|
+
assert isinstance(val, bool)
|
|
95
|
+
debugger.enabled = val
|
|
96
|
+
elif key == 'disable_varnames':
|
|
97
|
+
os.environ['NEOPRINT_DISABLE_VARNAMES'] = '1' if val else '0'
|
|
98
|
+
elif key == 'legacy_windows':
|
|
99
|
+
os.environ['NEOPRINT_LEGACY_WINDOWS'] = '1' if val else '0'
|
|
100
|
+
elif key == 'rich_traceback':
|
|
101
|
+
# assert isinstance(val, bool)
|
|
102
|
+
if val:
|
|
103
|
+
sys.excepthook = self._custom_excepthook
|
|
104
|
+
else:
|
|
105
|
+
sys.excepthook = _default_excepthook
|
|
106
|
+
|
|
107
|
+
def _custom_excepthook(self, type_, value, traceback) -> None:
|
|
108
|
+
# print(':r', '[red dim]drain out message queue[/]')
|
|
109
|
+
# from .logger import logger
|
|
110
|
+
# if hasattr(logger, '_stop_running'):
|
|
111
|
+
# logger._stop_running() # noqa
|
|
112
|
+
if type_ is KeyboardInterrupt:
|
|
113
|
+
# fmt: off
|
|
114
|
+
from .show import show
|
|
115
|
+
show(':v7', 'KeyboardInterrupt')
|
|
116
|
+
sys.exit(0)
|
|
117
|
+
# fmt: on
|
|
118
|
+
else:
|
|
119
|
+
# https://rich.readthedocs.io/en/stable/traceback.html
|
|
120
|
+
# dprint(getattr(self, 'show_traceback_locals'))
|
|
121
|
+
rich_console.print(
|
|
122
|
+
Traceback.from_exception(
|
|
123
|
+
type_,
|
|
124
|
+
value,
|
|
125
|
+
traceback,
|
|
126
|
+
show_locals=self.show_traceback_locals,
|
|
127
|
+
locals_hide_dunder=True,
|
|
128
|
+
locals_hide_sunder=True,
|
|
129
|
+
# word_wrap=True,
|
|
130
|
+
),
|
|
131
|
+
soft_wrap=False, # fixed line wrap problem.
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
config = _Config()
|
neoprint/console.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import typing as tp
|
|
5
|
+
from rich.console import Console as RichConsole
|
|
6
|
+
from .debugger import debugger
|
|
7
|
+
|
|
8
|
+
_stdout = sys.stdout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Console:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.width = self.get_console_width()
|
|
14
|
+
|
|
15
|
+
def get_console_width(self) -> int:
|
|
16
|
+
if hasattr(sys.stdout, 'columns'):
|
|
17
|
+
columns = sys.stdout.columns
|
|
18
|
+
if columns:
|
|
19
|
+
return int(columns) # type: ignore
|
|
20
|
+
if hasattr(os, 'get_terminal_size'):
|
|
21
|
+
try:
|
|
22
|
+
size = os.get_terminal_size()
|
|
23
|
+
if size.columns > 0:
|
|
24
|
+
return int(size.columns)
|
|
25
|
+
except OSError:
|
|
26
|
+
pass
|
|
27
|
+
fallback = os.environ.get('COLUMNS', '')
|
|
28
|
+
if fallback.isdigit():
|
|
29
|
+
return int(fallback)
|
|
30
|
+
return 80
|
|
31
|
+
|
|
32
|
+
def print(self, text: str, end: str = '\n', flush: bool = False) -> None:
|
|
33
|
+
if debugger.enabled:
|
|
34
|
+
debugger.output.append(text)
|
|
35
|
+
_stdout.write(text + end)
|
|
36
|
+
if flush:
|
|
37
|
+
_stdout.flush()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _LegacyRichConsole(RichConsole):
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
# https://github.com/Textualize/rich/issues/2622
|
|
43
|
+
super().__init__(
|
|
44
|
+
color_system='standard' if os.name == 'nt' else 'auto',
|
|
45
|
+
legacy_windows=True,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def capture_output(self, *args, **kwargs) -> str:
|
|
49
|
+
# https://chatgpt.com/share/6a16a585-0e00-8320-97ee-5fc2b572690e
|
|
50
|
+
with self.capture() as cap:
|
|
51
|
+
self.print(*args, **kwargs)
|
|
52
|
+
return cap.get()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _ModernRichConsole(_LegacyRichConsole):
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
RichConsole.__init__(
|
|
58
|
+
self,
|
|
59
|
+
color_system='standard' if os.name == 'nt' else 'auto',
|
|
60
|
+
legacy_windows=False, # make sure ansi color is used
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
console = Console()
|
|
65
|
+
# CONSOLE_WIDTH = console.width
|
|
66
|
+
rich_console = _ModernRichConsole()
|
|
67
|
+
legacy_rich_console = _LegacyRichConsole()
|
|
68
|
+
|
|
69
|
+
std_print = tp.cast(tp.Callable, builtins.print)
|
|
70
|
+
con_print = console.print
|
|
71
|
+
# con_error = partial(console.print_exception, word_wrap=True)
|
|
72
|
+
dbg_print = debugger.print
|
|
73
|
+
# non_print = NothingPrinter()
|
|
74
|
+
|
|
75
|
+
# alias
|
|
76
|
+
bprint = std_print
|
|
77
|
+
cprint = con_print
|
|
78
|
+
dprint = dbg_print
|
|
79
|
+
|
|
80
|
+
# default alias
|
|
81
|
+
# print = con_print
|
neoprint/control.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
|
|
3
|
+
from .console import bprint
|
|
4
|
+
from .frame_info import get_last_frame
|
|
5
|
+
from .show import show
|
|
6
|
+
|
|
7
|
+
effected_packages = set()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup(scope: str = 'this_package') -> None:
|
|
11
|
+
assert scope == 'this_package', f'scope `{scope}` is not supported'
|
|
12
|
+
frame = get_last_frame()
|
|
13
|
+
effected_packages.add(frame.package_name)
|
|
14
|
+
if getattr(builtins, 'print') is bprint:
|
|
15
|
+
setattr(builtins, 'print', _variable_print)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _variable_print(*args, **kwargs) -> None:
|
|
19
|
+
frame = get_last_frame()
|
|
20
|
+
if frame.package_name in effected_packages:
|
|
21
|
+
show(*args, _frame=frame, **kwargs)
|
|
22
|
+
else:
|
|
23
|
+
bprint(*args, **kwargs)
|
neoprint/debugger.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from inspect import currentframe
|
|
2
|
+
from rich.pretty import pprint
|
|
3
|
+
from .frame_info import FrameInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Debugger:
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self.enabled = False
|
|
9
|
+
self.output = []
|
|
10
|
+
|
|
11
|
+
def print(self, *args) -> None:
|
|
12
|
+
frame = FrameInfo(currentframe().f_back) # type: ignore
|
|
13
|
+
pprint(
|
|
14
|
+
('[debug]:{}:{}'.format(frame.file_name, frame.line_number), *args)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
debugger = _Debugger()
|
neoprint/format.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import typing as tp
|
|
2
|
+
from inspect import currentframe
|
|
3
|
+
|
|
4
|
+
from . import text_object as to
|
|
5
|
+
from .config import config
|
|
6
|
+
from .console import dprint # noqa
|
|
7
|
+
from .frame_info import FrameInfo
|
|
8
|
+
from .markup import Mark
|
|
9
|
+
from .markup import markup_analyzer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class T: # Typehint
|
|
13
|
+
Args = tp.Tuple[tp.Any, ...]
|
|
14
|
+
FlushScheme = int
|
|
15
|
+
# 0: no flush
|
|
16
|
+
# 1: instant flush
|
|
17
|
+
# 2: instant flush and drain
|
|
18
|
+
# 3: wait for flush
|
|
19
|
+
Marks = tp.Dict[str, tp.Any]
|
|
20
|
+
Markup = str
|
|
21
|
+
MarkupPos = int # -1, 0, 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def format_list(
|
|
25
|
+
*args,
|
|
26
|
+
markup: tp.Optional[T.Markup] = None,
|
|
27
|
+
_elevate_parent_level: int = 0,
|
|
28
|
+
_frame: tp.Optional[FrameInfo] = None,
|
|
29
|
+
_mark_position: int = 0,
|
|
30
|
+
) -> tp.List[to.TextObject]:
|
|
31
|
+
"""
|
|
32
|
+
frame relationship:
|
|
33
|
+
this_frame: frame to this function.
|
|
34
|
+
foreign_frame: frame to first call of foreign function.
|
|
35
|
+
for example:
|
|
36
|
+
1.
|
|
37
|
+
# aaa.py
|
|
38
|
+
import neoprint as np # ln1
|
|
39
|
+
np.format_list(...) # ln2
|
|
40
|
+
# foreign_frame is `aaa.py:2`
|
|
41
|
+
2.
|
|
42
|
+
# neoprint/show.py
|
|
43
|
+
def show(...):
|
|
44
|
+
x = format_list(
|
|
45
|
+
..., _frame=FrameInfo(currentframe().f_back)
|
|
46
|
+
)
|
|
47
|
+
# foreign_frame is the one who is calling `show(...)`.
|
|
48
|
+
target_frame: frame to the most proper place.
|
|
49
|
+
if ':p' markup is not used, target_frame is foreign_frame.
|
|
50
|
+
if ':p' markup is set, target_frame = `foreign_frame.f_back_to_:p`.
|
|
51
|
+
|
|
52
|
+
how does caller make foreign_frame:
|
|
53
|
+
caller can set _frame directly, or set _elevate_parent_level.
|
|
54
|
+
the following are same:
|
|
55
|
+
def foo():
|
|
56
|
+
format_list(..., _frame=FrameInfo(currentframe().f_back))
|
|
57
|
+
def bar():
|
|
58
|
+
format_list(..., _elevate_parent_level=1)
|
|
59
|
+
if both params are set, _frame will be used.
|
|
60
|
+
"""
|
|
61
|
+
if _frame is None:
|
|
62
|
+
this_frame = FrameInfo(currentframe()) # type: ignore
|
|
63
|
+
foreign_frame = this_frame.get_parent(1 + _elevate_parent_level)
|
|
64
|
+
else:
|
|
65
|
+
foreign_frame = _frame
|
|
66
|
+
target_frame = foreign_frame
|
|
67
|
+
|
|
68
|
+
if markup is None:
|
|
69
|
+
args, markpos, markup = extract_markup_from_arguments(args)
|
|
70
|
+
else:
|
|
71
|
+
markpos = _mark_position
|
|
72
|
+
# dprint(args, markup, args[-1], markup_analyzer.is_valid_markup(args[-1]))
|
|
73
|
+
marks = markup_analyzer.analyze(markup, foreign_frame)
|
|
74
|
+
|
|
75
|
+
if marks['p']:
|
|
76
|
+
target_frame = foreign_frame.get_parent(marks['p'])
|
|
77
|
+
assert target_frame
|
|
78
|
+
|
|
79
|
+
# --------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
result = []
|
|
82
|
+
|
|
83
|
+
# head part
|
|
84
|
+
head_parts = get_head_parts(target_frame)
|
|
85
|
+
result.extend(head_parts)
|
|
86
|
+
|
|
87
|
+
# --------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
# body part
|
|
90
|
+
before_body_parts = []
|
|
91
|
+
body_parts = []
|
|
92
|
+
|
|
93
|
+
if marks['i']:
|
|
94
|
+
before_body_parts.append(to.Index(marks['i']))
|
|
95
|
+
before_body_parts.append(to.Space())
|
|
96
|
+
|
|
97
|
+
if marks['n']:
|
|
98
|
+
varnames = foreign_frame.varnames
|
|
99
|
+
if markpos:
|
|
100
|
+
varnames = varnames[1:] if markpos == 1 else varnames[:-1]
|
|
101
|
+
# assert len(varnames) == len(args), (varnames, args)
|
|
102
|
+
if len(varnames) != len(args):
|
|
103
|
+
# this may because user has modified the source code after call.
|
|
104
|
+
# we should refresh the AST to get new varnames.
|
|
105
|
+
foreign_frame.refresh()
|
|
106
|
+
varnames = foreign_frame.varnames
|
|
107
|
+
assert len(varnames) == len(args), (varnames, args)
|
|
108
|
+
else:
|
|
109
|
+
varnames = (None,) * len(args)
|
|
110
|
+
|
|
111
|
+
for name, arg in zip(varnames, args):
|
|
112
|
+
if name is None:
|
|
113
|
+
body_parts.append(to.Text(arg))
|
|
114
|
+
else:
|
|
115
|
+
body_parts.append(to.NamedVariable(name, arg))
|
|
116
|
+
body_parts.append(to.InBodySeparator())
|
|
117
|
+
body_parts.append(to.Space())
|
|
118
|
+
body_parts = body_parts[:-2]
|
|
119
|
+
|
|
120
|
+
if marks['l'] or marks['r']:
|
|
121
|
+
# there are three cases:
|
|
122
|
+
# l1 and r*: expand object
|
|
123
|
+
# l2 or r2: special expand object
|
|
124
|
+
# l0 and r1: bbcode object
|
|
125
|
+
if marks['l'] == Mark.EXPAND_FORMAT:
|
|
126
|
+
body_parts = [
|
|
127
|
+
to.ExpandedObject(x)
|
|
128
|
+
if to.ExpandedObject.check_expandable(x)
|
|
129
|
+
else x
|
|
130
|
+
for x in body_parts
|
|
131
|
+
]
|
|
132
|
+
elif (
|
|
133
|
+
marks['l'] == Mark.SPECIAL_EXPAND_FORMAT
|
|
134
|
+
or marks['r'] == Mark.RICH_OBJECT
|
|
135
|
+
):
|
|
136
|
+
body_parts = [
|
|
137
|
+
to.SpecialExpandedObject(x)
|
|
138
|
+
if to.SpecialExpandedObject.check_expandable(x)
|
|
139
|
+
else to.ExpandedObject(x)
|
|
140
|
+
if to.ExpandedObject.check_expandable(x)
|
|
141
|
+
else x
|
|
142
|
+
for x in body_parts
|
|
143
|
+
]
|
|
144
|
+
elif marks['r'] == Mark.RICH_FORMAT:
|
|
145
|
+
... # TODO
|
|
146
|
+
else:
|
|
147
|
+
raise Exception('unreachable case')
|
|
148
|
+
body_parts = [
|
|
149
|
+
to.ExpandedObjectGroup(body_parts, head_parts + before_body_parts)
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
if marks['d']:
|
|
153
|
+
body_parts = [
|
|
154
|
+
to.DividerLine(
|
|
155
|
+
body_parts,
|
|
156
|
+
head_parts + before_body_parts,
|
|
157
|
+
bold=marks['d'] == Mark.THICK_DIVIDER_LINE,
|
|
158
|
+
)
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
if marks['v']:
|
|
162
|
+
global_color, global_style = marks['v']
|
|
163
|
+
for part in body_parts:
|
|
164
|
+
if part.editable:
|
|
165
|
+
# dprint(part, global_color, global_style)
|
|
166
|
+
part.color = global_color
|
|
167
|
+
part.style = global_style
|
|
168
|
+
|
|
169
|
+
result.extend(before_body_parts)
|
|
170
|
+
result.extend(body_parts)
|
|
171
|
+
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def format(*args, markup: tp.Optional[str] = None) -> str:
|
|
176
|
+
if markup is None:
|
|
177
|
+
args, markpos, markup = extract_markup_from_arguments(args)
|
|
178
|
+
else:
|
|
179
|
+
markpos, markup = 0, markup
|
|
180
|
+
result = format_list(
|
|
181
|
+
*args, markup=markup, _elevate_parent_level=1, _mark_position=markpos
|
|
182
|
+
)
|
|
183
|
+
return ''.join(p.render(color_code_scheme='none') for p in result)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ------------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def extract_markup_from_arguments(
|
|
190
|
+
args: T.Args,
|
|
191
|
+
) -> tp.Tuple[T.Args, int, T.Markup]:
|
|
192
|
+
if (
|
|
193
|
+
len(args) > 0
|
|
194
|
+
and isinstance(args[0], str)
|
|
195
|
+
and args[0].startswith(':')
|
|
196
|
+
and markup_analyzer.is_valid_markup(args[0])
|
|
197
|
+
):
|
|
198
|
+
return args[1:], 1, args[0]
|
|
199
|
+
elif (
|
|
200
|
+
len(args) > 1
|
|
201
|
+
and isinstance(args[-1], str)
|
|
202
|
+
and args[-1].startswith(':')
|
|
203
|
+
and markup_analyzer.is_valid_markup(args[-1])
|
|
204
|
+
):
|
|
205
|
+
return args[:-1], -1, args[-1]
|
|
206
|
+
else:
|
|
207
|
+
return args, 0, ''
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_head_parts(frame: FrameInfo) -> tp.List[to.TextObject]:
|
|
211
|
+
out = []
|
|
212
|
+
if config.show_source:
|
|
213
|
+
out.append(to.Source(frame))
|
|
214
|
+
if config.show_funcname:
|
|
215
|
+
if out:
|
|
216
|
+
out.append(to.Space())
|
|
217
|
+
out.append(to.FuncnameSeparator())
|
|
218
|
+
out.append(to.Space())
|
|
219
|
+
out.append(...) # TODO
|
|
220
|
+
if out:
|
|
221
|
+
out.append(to.Space())
|
|
222
|
+
out.append(to.BodySeparator())
|
|
223
|
+
out.append(to.Space())
|
|
224
|
+
return out
|
neoprint/frame_info.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import typing as tp
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from types import FrameType
|
|
5
|
+
|
|
6
|
+
from . import sourcemap
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FrameInfo:
|
|
10
|
+
file_name: str
|
|
11
|
+
file_path: str
|
|
12
|
+
function_name: str
|
|
13
|
+
line_number: int
|
|
14
|
+
package_name: str
|
|
15
|
+
_frame: FrameType
|
|
16
|
+
_varnames: tp.Optional[tp.Tuple[tp.Optional[str], ...]]
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def this_place(cls) -> 'FrameInfo':
|
|
20
|
+
return cls(inspect.currentframe().f_back) # type: ignore
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def parent_place(cls) -> 'FrameInfo':
|
|
24
|
+
return cls(inspect.currentframe().f_back.f_back) # type: ignore
|
|
25
|
+
|
|
26
|
+
def __init__(self, frame: FrameType) -> None:
|
|
27
|
+
self._frame = frame
|
|
28
|
+
self.refresh()
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return self.info
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def id(self) -> str:
|
|
35
|
+
return f'{self.file_path}:{self.line_number}'
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def indentation(self) -> int:
|
|
39
|
+
# https://stackoverflow.com/a/39172552
|
|
40
|
+
if x := inspect.getframeinfo(self._frame).code_context:
|
|
41
|
+
ctx = x[0]
|
|
42
|
+
return len(ctx) - len(ctx.lstrip())
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def info(self) -> str:
|
|
47
|
+
return dedent(
|
|
48
|
+
f"""
|
|
49
|
+
<FrameInfo object
|
|
50
|
+
filepath: {self.file_path}
|
|
51
|
+
lineno: {self.line_number}
|
|
52
|
+
funcname: {self.function_name}
|
|
53
|
+
>
|
|
54
|
+
"""
|
|
55
|
+
).rstrip()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def parent(self) -> 'FrameInfo':
|
|
59
|
+
return self.get_parent(1)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def varnames(self) -> tp.Sequence[tp.Optional[str]]:
|
|
63
|
+
if self._varnames is None:
|
|
64
|
+
self._varnames = tuple(self.collect_varnames())
|
|
65
|
+
return self._varnames
|
|
66
|
+
|
|
67
|
+
def collect_varnames(self) -> tp.Sequence[tp.Optional[str]]:
|
|
68
|
+
return sourcemap.get_varnames(self.file_path, self.line_number)
|
|
69
|
+
|
|
70
|
+
def get_parent(self, traceback_level: int = 1) -> 'FrameInfo':
|
|
71
|
+
frame = self._frame
|
|
72
|
+
for _ in range(traceback_level):
|
|
73
|
+
frame = frame.f_back # type: ignore
|
|
74
|
+
return FrameInfo(frame) # type: ignore
|
|
75
|
+
|
|
76
|
+
def refresh(self) -> None:
|
|
77
|
+
frame = self._frame
|
|
78
|
+
self.function_name = frame.f_code.co_name
|
|
79
|
+
self.package_name = frame.f_globals['__name__'].split('.', 1)[0]
|
|
80
|
+
self.file_path = (
|
|
81
|
+
frame.f_globals.get('__file__', frame.f_code.co_filename)
|
|
82
|
+
# note:
|
|
83
|
+
# - path may be "<string>", "<unknown>" etc.
|
|
84
|
+
# - path may be "<ipython-input-10-5abb16185f48>" in ipython
|
|
85
|
+
# environment.
|
|
86
|
+
# - `co_filename` may be a relative python in python 3.8.
|
|
87
|
+
# - path may not exist.
|
|
88
|
+
).replace('\\', '/')
|
|
89
|
+
self.file_name = (
|
|
90
|
+
self.file_path
|
|
91
|
+
if self.file_path[0] == '<'
|
|
92
|
+
else self.file_path.rsplit('/', 1)[-1]
|
|
93
|
+
)
|
|
94
|
+
self.line_number = frame.f_lineno
|
|
95
|
+
self._varnames = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_last_frame() -> FrameInfo:
|
|
99
|
+
frame = inspect.currentframe().f_back.f_back # type: ignore
|
|
100
|
+
return FrameInfo(frame) # type: ignore
|