kalong 0.4.3__py3-none-any.whl → 0.5.1__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.
- kalong/__init__.py +59 -11
- kalong/__main__.py +3 -38
- kalong/communication.py +127 -103
- kalong/config.py +7 -5
- kalong/debugger.py +86 -24
- kalong/forking.py +1 -3
- kalong/loops.py +2 -2
- kalong/server.py +5 -14
- kalong/static/assets/index-BwfqANhu.js +226 -0
- kalong/static/index.html +3 -4
- kalong/stepping.py +21 -3
- kalong/tracing.py +12 -14
- kalong/utils/__init__.py +12 -3
- kalong/utils/doc_lookup/lookup.json +9861 -1
- kalong/utils/iterators.py +14 -0
- kalong/utils/obj.py +6 -16
- kalong/websockets.py +5 -14
- kalong-0.5.1.dist-info/METADATA +22 -0
- kalong-0.5.1.dist-info/RECORD +28 -0
- {kalong-0.4.3.dist-info → kalong-0.5.1.dist-info}/WHEEL +1 -1
- kalong-0.5.1.dist-info/entry_points.txt +2 -0
- kalong/static/assets/index-666d143f.js +0 -196
- kalong-0.4.3.dist-info/METADATA +0 -27
- kalong-0.4.3.dist-info/RECORD +0 -27
- /kalong/static/assets/{FiraCode-Bold-38f445df.otf → FiraCode-Bold-Ba6ukUQM.otf} +0 -0
- /kalong/static/assets/{FiraCode-Regular-825cd631.otf → FiraCode-Regular-DP3ilQdk.otf} +0 -0
- /kalong/static/assets/{favicon-208a422a.svg → favicon-rdSYpj7B.svg} +0 -0
- {kalong-0.4.3.dist-info → kalong-0.5.1.dist-info/licenses}/LICENSE +0 -0
kalong/__init__.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""A new take on debugging"""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
__version__ = "0.5.1"
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from subprocess import run
|
|
5
8
|
|
|
6
9
|
from .config import Config
|
|
7
10
|
|
|
@@ -31,12 +34,14 @@ def break_above(level):
|
|
|
31
34
|
start_trace(frame)
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
def start_trace(break_=False, full=False):
|
|
37
|
+
def start_trace(break_=False, full=False, skip_frames=None):
|
|
35
38
|
from .stepping import add_step, start_trace
|
|
36
39
|
|
|
37
40
|
frame = sys._getframe().f_back
|
|
38
41
|
add_step(
|
|
39
|
-
"stepInto" if break_ else ("trace" if full else "continue"),
|
|
42
|
+
"stepInto" if break_ else ("trace" if full else "continue"),
|
|
43
|
+
frame,
|
|
44
|
+
skip_frames,
|
|
40
45
|
)
|
|
41
46
|
start_trace(frame)
|
|
42
47
|
|
|
@@ -49,23 +54,23 @@ def stop_trace():
|
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
class trace:
|
|
52
|
-
def __init__(self,
|
|
53
|
-
self.
|
|
54
|
-
self.full = full
|
|
57
|
+
def __init__(self, **kwargs):
|
|
58
|
+
self.kwargs = kwargs
|
|
55
59
|
|
|
56
60
|
def __enter__(self):
|
|
57
|
-
|
|
61
|
+
stop_trace()
|
|
62
|
+
start_trace(**self.kwargs)
|
|
58
63
|
|
|
59
64
|
def __exit__(self, *args):
|
|
60
65
|
stop_trace()
|
|
61
66
|
|
|
62
67
|
|
|
63
|
-
def run_file(filename, *args):
|
|
64
|
-
from .utils import fake_argv
|
|
65
|
-
|
|
68
|
+
def run_file(filename, *args, break_=True):
|
|
66
69
|
# Cleaning __main__ namespace
|
|
67
70
|
import __main__
|
|
68
71
|
|
|
72
|
+
from .utils import fake_argv
|
|
73
|
+
|
|
69
74
|
__main__.__dict__.clear()
|
|
70
75
|
__main__.__dict__.update(
|
|
71
76
|
{
|
|
@@ -82,7 +87,7 @@ def run_file(filename, *args):
|
|
|
82
87
|
globals = __main__.__dict__
|
|
83
88
|
locals = globals
|
|
84
89
|
with fake_argv(filename, *args):
|
|
85
|
-
with trace(break_=
|
|
90
|
+
with trace(break_=break_):
|
|
86
91
|
exec(statement, globals, locals)
|
|
87
92
|
|
|
88
93
|
|
|
@@ -93,3 +98,46 @@ def shell():
|
|
|
93
98
|
frame = sys._getframe()
|
|
94
99
|
# Enter the websocket communication loop that pauses the execution
|
|
95
100
|
communicate(frame, "shell", [])
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
os.environ["PYTHONBREAKPOINT"] = "kalong.breakpoint"
|
|
105
|
+
config.from_args()
|
|
106
|
+
|
|
107
|
+
if config.server:
|
|
108
|
+
from .server import serve
|
|
109
|
+
|
|
110
|
+
serve()
|
|
111
|
+
|
|
112
|
+
elif config.inject:
|
|
113
|
+
kalong_dir = Path(__file__).resolve().parent.parent
|
|
114
|
+
gdb_command = (
|
|
115
|
+
["gdb", "-p", str(config.inject), "-batch"]
|
|
116
|
+
+ [
|
|
117
|
+
"-eval-command=call %s" % hook
|
|
118
|
+
for hook in [
|
|
119
|
+
"(int) PyGILState_Ensure()", # Getting the GIL
|
|
120
|
+
'(int) PyRun_SimpleString("'
|
|
121
|
+
f"print('* Kalong injection from {os.getpid()} *');"
|
|
122
|
+
"import sys;" # Putting kalong project directory in sys path:
|
|
123
|
+
f"sys.path.insert(0, '{kalong_dir}');"
|
|
124
|
+
"import kalong;" # Setting breakpoint:
|
|
125
|
+
"kalong.break_above(2);"
|
|
126
|
+
'")',
|
|
127
|
+
# Releasing the GIL with the PyGILState_Ensure handle:
|
|
128
|
+
"(void) PyGILState_Release($1)",
|
|
129
|
+
]
|
|
130
|
+
]
|
|
131
|
+
)
|
|
132
|
+
print(f'Running: {" ".join(gdb_command)}')
|
|
133
|
+
run(gdb_command)
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
if config.command:
|
|
137
|
+
run_file(*config.command, break_=config.break_at_start)
|
|
138
|
+
else:
|
|
139
|
+
shell()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|
kalong/__main__.py
CHANGED
|
@@ -1,39 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from subprocess import run
|
|
1
|
+
from kalong import main
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
os.environ["PYTHONBREAKPOINT"] = "kalong.breakpoint"
|
|
8
|
-
config.from_args()
|
|
9
|
-
|
|
10
|
-
if config.server:
|
|
11
|
-
from .server import serve
|
|
12
|
-
|
|
13
|
-
serve()
|
|
14
|
-
|
|
15
|
-
elif config.inject:
|
|
16
|
-
kalong_dir = Path(__file__).resolve().parent.parent
|
|
17
|
-
gdb_command = ["gdb", "-p", str(config.inject), "-batch"] + [
|
|
18
|
-
"-eval-command=call %s" % hook
|
|
19
|
-
for hook in [
|
|
20
|
-
"(int) PyGILState_Ensure()", # Getting the GIL
|
|
21
|
-
'(int) PyRun_SimpleString("'
|
|
22
|
-
f"print('* Kalong injection from {os.getpid()} *');"
|
|
23
|
-
"import sys;" # Putting kalong project directory in sys path:
|
|
24
|
-
f"sys.path.insert(0, '{kalong_dir}');"
|
|
25
|
-
"import kalong;" # Setting breakpoint:
|
|
26
|
-
"kalong.break_above(2);"
|
|
27
|
-
'")',
|
|
28
|
-
# Releasing the GIL with the PyGILState_Ensure handle:
|
|
29
|
-
"(void) PyGILState_Release($1)",
|
|
30
|
-
]
|
|
31
|
-
]
|
|
32
|
-
print(f'Running: {" ".join(gdb_command)}')
|
|
33
|
-
run(gdb_command)
|
|
34
|
-
|
|
35
|
-
else:
|
|
36
|
-
if config.command:
|
|
37
|
-
run_file(*config.command)
|
|
38
|
-
else:
|
|
39
|
-
shell()
|
|
3
|
+
if __name__ == "__main__":
|
|
4
|
+
main()
|
kalong/communication.py
CHANGED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import linecache
|
|
4
4
|
import logging
|
|
5
|
+
import sys
|
|
5
6
|
import threading
|
|
6
7
|
|
|
7
8
|
from aiohttp import WSMsgType
|
|
@@ -11,26 +12,35 @@ from .debugger import (
|
|
|
11
12
|
get_frame,
|
|
12
13
|
get_title,
|
|
13
14
|
serialize_answer,
|
|
15
|
+
serialize_answer_recursive,
|
|
14
16
|
serialize_diff_eval,
|
|
17
|
+
serialize_exception,
|
|
15
18
|
serialize_frames,
|
|
16
19
|
serialize_inspect,
|
|
17
20
|
serialize_inspect_eval,
|
|
18
21
|
serialize_suggestion,
|
|
19
22
|
serialize_table,
|
|
20
23
|
)
|
|
24
|
+
from .errors import SetFrameError
|
|
21
25
|
from .loops import get_loop
|
|
22
26
|
from .stepping import add_step, clear_step, stop_trace
|
|
23
27
|
from .utils import basicConfig, get_file_from_code
|
|
24
28
|
from .websockets import die, websocket_state
|
|
25
|
-
from .errors import SetFrameError
|
|
26
29
|
|
|
27
30
|
log = logging.getLogger(__name__)
|
|
28
31
|
basicConfig(level=config.log_level)
|
|
29
32
|
|
|
30
33
|
|
|
34
|
+
def exception_handler(loop, context):
|
|
35
|
+
exception = context["exception"]
|
|
36
|
+
message = context["message"]
|
|
37
|
+
logging.error(f"Task failed, msg={message}, exception={exception}")
|
|
38
|
+
|
|
39
|
+
|
|
31
40
|
def communicate(frame, event, arg):
|
|
32
41
|
loop = get_loop()
|
|
33
42
|
|
|
43
|
+
loop.set_exception_handler(exception_handler)
|
|
34
44
|
try:
|
|
35
45
|
loop.run_until_complete(communication_loop(frame, event, arg))
|
|
36
46
|
except asyncio.CancelledError:
|
|
@@ -42,19 +52,116 @@ async def init(ws, frame, event, arg):
|
|
|
42
52
|
tb = arg[2] if event == "exception" else None
|
|
43
53
|
|
|
44
54
|
await ws.send_json({"type": "SET_THEME", "theme": event})
|
|
45
|
-
await ws.send_json(
|
|
46
|
-
{"type": "SET_TITLE", "title": get_title(frame, event, arg)}
|
|
47
|
-
)
|
|
55
|
+
await ws.send_json({"type": "SET_TITLE", "title": get_title(frame, event, arg)})
|
|
48
56
|
await ws.send_json(
|
|
49
57
|
{
|
|
50
58
|
"type": "SET_FRAMES",
|
|
51
|
-
"frames": list(serialize_frames(frame, tb))
|
|
52
|
-
if event != "shell"
|
|
53
|
-
else [],
|
|
59
|
+
"frames": list(serialize_frames(frame, tb)) if event != "shell" else [],
|
|
54
60
|
}
|
|
55
61
|
)
|
|
56
62
|
|
|
57
63
|
|
|
64
|
+
async def handle_message(ws, data, frame, event, arg):
|
|
65
|
+
tb = arg[2] if event == "exception" else None
|
|
66
|
+
if data["type"] == "HELLO":
|
|
67
|
+
await init(ws, frame, event, arg)
|
|
68
|
+
response = {
|
|
69
|
+
"type": "SET_INFO",
|
|
70
|
+
"config": config.__dict__,
|
|
71
|
+
"main": threading.current_thread() is threading.main_thread(),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
elif data["type"] == "GET_FILE":
|
|
75
|
+
filename = data["filename"]
|
|
76
|
+
file = "".join(linecache.getlines(filename))
|
|
77
|
+
if not file:
|
|
78
|
+
file = get_file_from_code(frame, filename)
|
|
79
|
+
response = {
|
|
80
|
+
"type": "SET_FILE",
|
|
81
|
+
"filename": filename,
|
|
82
|
+
"source": file,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
elif data["type"] == "SET_PROMPT" or data["type"] == "REFRESH_PROMPT":
|
|
86
|
+
try:
|
|
87
|
+
eval_fun = (
|
|
88
|
+
serialize_inspect_eval
|
|
89
|
+
if data.get("command") == "inspect"
|
|
90
|
+
else serialize_diff_eval
|
|
91
|
+
if data.get("command") == "diff"
|
|
92
|
+
else serialize_table
|
|
93
|
+
if data.get("command") == "table"
|
|
94
|
+
else serialize_answer_recursive
|
|
95
|
+
if data.get("command") == "recursive_debug"
|
|
96
|
+
else serialize_answer
|
|
97
|
+
)
|
|
98
|
+
response = {
|
|
99
|
+
"type": "SET_ANSWER",
|
|
100
|
+
"key": data["key"],
|
|
101
|
+
"command": data.get("command"),
|
|
102
|
+
"frame": data.get("frame"),
|
|
103
|
+
**eval_fun(
|
|
104
|
+
data["prompt"],
|
|
105
|
+
get_frame(frame, data.get("frame"), tb),
|
|
106
|
+
),
|
|
107
|
+
}
|
|
108
|
+
except SetFrameError as e:
|
|
109
|
+
frame = e.frame
|
|
110
|
+
event = e.event
|
|
111
|
+
arg = e.arg
|
|
112
|
+
|
|
113
|
+
await init(ws, frame, event, arg)
|
|
114
|
+
|
|
115
|
+
response = {
|
|
116
|
+
"type": "SET_ANSWER",
|
|
117
|
+
"key": data["key"],
|
|
118
|
+
"command": data.get("command"),
|
|
119
|
+
"frame": data.get("frame"),
|
|
120
|
+
"prompt": data["prompt"].strip(),
|
|
121
|
+
"answer": "",
|
|
122
|
+
"duration": 0,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
elif data["type"] == "REQUEST_INSPECT":
|
|
126
|
+
response = {
|
|
127
|
+
"type": "SET_ANSWER",
|
|
128
|
+
"key": data["key"],
|
|
129
|
+
"command": data.get("command"),
|
|
130
|
+
**serialize_inspect(data["id"]),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
elif data["type"] == "REQUEST_SUGGESTION":
|
|
134
|
+
response = {
|
|
135
|
+
"type": "SET_SUGGESTION",
|
|
136
|
+
**serialize_suggestion(
|
|
137
|
+
data["prompt"],
|
|
138
|
+
data["from"],
|
|
139
|
+
data["to"],
|
|
140
|
+
data["cursor"],
|
|
141
|
+
get_frame(frame, data.get("frame"), tb),
|
|
142
|
+
),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
elif data["type"] == "DO_COMMAND":
|
|
146
|
+
command = data["command"]
|
|
147
|
+
response = {"type": "ACK", "command": command}
|
|
148
|
+
if command == "run":
|
|
149
|
+
clear_step()
|
|
150
|
+
stop_trace(frame)
|
|
151
|
+
elif command == "stop":
|
|
152
|
+
clear_step()
|
|
153
|
+
stop_trace(frame)
|
|
154
|
+
die()
|
|
155
|
+
else:
|
|
156
|
+
step_frame = get_frame(frame, data.get("frame"), tb)
|
|
157
|
+
add_step(command, step_frame)
|
|
158
|
+
response["stop"] = True
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(f"Unknown type {data['type']}")
|
|
162
|
+
return response
|
|
163
|
+
|
|
164
|
+
|
|
58
165
|
async def communication_loop(frame_, event_, arg_):
|
|
59
166
|
frame = frame_
|
|
60
167
|
event = event_
|
|
@@ -77,110 +184,27 @@ async def communication_loop(frame_, event_, arg_):
|
|
|
77
184
|
async for msg in ws:
|
|
78
185
|
if msg.type == WSMsgType.TEXT:
|
|
79
186
|
data = json.loads(msg.data)
|
|
80
|
-
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"config": config.__dict__,
|
|
85
|
-
"main": threading.current_thread()
|
|
86
|
-
is threading.main_thread(),
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
elif data["type"] == "GET_FILE":
|
|
90
|
-
filename = data["filename"]
|
|
91
|
-
file = "".join(linecache.getlines(filename))
|
|
92
|
-
if not file:
|
|
93
|
-
file = get_file_from_code(frame, filename)
|
|
94
|
-
response = {
|
|
95
|
-
"type": "SET_FILE",
|
|
96
|
-
"filename": filename,
|
|
97
|
-
"source": file,
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
elif (
|
|
101
|
-
data["type"] == "SET_PROMPT"
|
|
102
|
-
or data["type"] == "REFRESH_PROMPT"
|
|
103
|
-
):
|
|
104
|
-
try:
|
|
105
|
-
eval_fun = (
|
|
106
|
-
serialize_inspect_eval
|
|
107
|
-
if data.get("command") == "inspect"
|
|
108
|
-
else serialize_diff_eval
|
|
109
|
-
if data.get("command") == "diff"
|
|
110
|
-
else serialize_table
|
|
111
|
-
if data.get("command") == "table"
|
|
112
|
-
else serialize_answer
|
|
113
|
-
)
|
|
114
|
-
response = {
|
|
115
|
-
"type": "SET_ANSWER",
|
|
116
|
-
"key": data["key"],
|
|
117
|
-
"command": data.get("command"),
|
|
118
|
-
"frame": data.get("frame"),
|
|
119
|
-
**eval_fun(
|
|
120
|
-
data["prompt"],
|
|
121
|
-
get_frame(frame, data.get("frame")),
|
|
122
|
-
),
|
|
123
|
-
}
|
|
124
|
-
except SetFrameError as e:
|
|
125
|
-
frame = e.frame
|
|
126
|
-
event = e.event
|
|
127
|
-
arg = e.arg
|
|
128
|
-
|
|
129
|
-
await init(ws, frame, event, arg)
|
|
130
|
-
|
|
131
|
-
response = {
|
|
132
|
-
"type": "SET_ANSWER",
|
|
133
|
-
"key": data["key"],
|
|
134
|
-
"command": data.get("command"),
|
|
135
|
-
"frame": data.get("frame"),
|
|
136
|
-
"prompt": data["prompt"].strip(),
|
|
137
|
-
"answer": "",
|
|
138
|
-
"duration": 0,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
elif data["type"] == "REQUEST_INSPECT":
|
|
187
|
+
try:
|
|
188
|
+
response = await handle_message(ws, data, frame, event, arg)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
log.error(f"Error handling message {data}", exc_info=e)
|
|
142
191
|
response = {
|
|
143
192
|
"type": "SET_ANSWER",
|
|
193
|
+
"prompt": data.get("prompt", "?").strip(),
|
|
144
194
|
"key": data["key"],
|
|
145
195
|
"command": data.get("command"),
|
|
146
|
-
|
|
196
|
+
"frame": data.get("frame"),
|
|
197
|
+
"answer": [serialize_exception(*sys.exc_info(), "internal")],
|
|
147
198
|
}
|
|
199
|
+
log.debug(f"Got {data} answering with {response}")
|
|
200
|
+
response["local"] = True
|
|
148
201
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
**serialize_suggestion(
|
|
153
|
-
data["prompt"],
|
|
154
|
-
data["from"],
|
|
155
|
-
data["to"],
|
|
156
|
-
data["cursor"],
|
|
157
|
-
get_frame(frame, data.get("frame")),
|
|
158
|
-
),
|
|
159
|
-
}
|
|
202
|
+
if response.pop("recursive", False):
|
|
203
|
+
await ws.send_json({"type": "PAUSE", "recursive": True})
|
|
204
|
+
await init(ws, frame, event, arg)
|
|
160
205
|
|
|
161
|
-
|
|
162
|
-
command = data["command"]
|
|
163
|
-
response = {"type": "ACK", "command": command}
|
|
164
|
-
if command == "run":
|
|
165
|
-
clear_step()
|
|
166
|
-
stop_trace(frame)
|
|
167
|
-
elif command == "stop":
|
|
168
|
-
clear_step()
|
|
169
|
-
stop_trace(frame)
|
|
170
|
-
die()
|
|
171
|
-
else:
|
|
172
|
-
step_frame = get_frame(frame, data.get("frame"))
|
|
173
|
-
add_step(command, step_frame)
|
|
174
|
-
stop = True
|
|
175
|
-
|
|
176
|
-
else:
|
|
177
|
-
response = {
|
|
178
|
-
"type": "error",
|
|
179
|
-
"message": f"Unknown type {data['type']}",
|
|
180
|
-
}
|
|
206
|
+
stop = response.pop("stop", False)
|
|
181
207
|
|
|
182
|
-
log.debug(f"Got {data} answering with {response}")
|
|
183
|
-
response["local"] = True
|
|
184
208
|
try:
|
|
185
209
|
await ws.send_json(response)
|
|
186
210
|
except ConnectionResetError:
|
kalong/config.py
CHANGED
|
@@ -24,17 +24,14 @@ class Config:
|
|
|
24
24
|
parser.add_argument(
|
|
25
25
|
"--server",
|
|
26
26
|
action="store_true",
|
|
27
|
-
help="Launch the kalong server. "
|
|
28
|
-
"This option is used by kalong itself",
|
|
27
|
+
help="Launch the kalong server. " "This option is used by kalong itself",
|
|
29
28
|
)
|
|
30
29
|
parser.add_argument(
|
|
31
30
|
"--protocol",
|
|
32
31
|
default=self.protocol,
|
|
33
32
|
help="Protocol for contacting kalong server",
|
|
34
33
|
)
|
|
35
|
-
parser.add_argument(
|
|
36
|
-
"--host", default=self.host, help="Host of kalong server"
|
|
37
|
-
)
|
|
34
|
+
parser.add_argument("--host", default=self.host, help="Host of kalong server")
|
|
38
35
|
parser.add_argument(
|
|
39
36
|
"--port", type=int, default=self.port, help="Port of kalong server"
|
|
40
37
|
)
|
|
@@ -64,6 +61,11 @@ class Config:
|
|
|
64
61
|
help="Pid of a running process in which a debugger will be "
|
|
65
62
|
"injected with gdb. This needs a working gdb and ptrace enabled",
|
|
66
63
|
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--break-at-start",
|
|
66
|
+
action="store_true",
|
|
67
|
+
help="Break at the start of the python file",
|
|
68
|
+
)
|
|
67
69
|
parser.add_argument(
|
|
68
70
|
"command",
|
|
69
71
|
nargs=REMAINDER,
|
kalong/debugger.py
CHANGED
|
@@ -21,9 +21,10 @@ from pprint import pformat
|
|
|
21
21
|
|
|
22
22
|
from jedi import Interpreter
|
|
23
23
|
|
|
24
|
-
from .
|
|
24
|
+
from .errors import SetFrameError
|
|
25
|
+
from .utils import cutter_mock, dedent, discompile, universal_travel
|
|
25
26
|
from .utils.io import capture_display, capture_exception, capture_std
|
|
26
|
-
from .utils.iterators import iter_cause, iter_stack
|
|
27
|
+
from .utils.iterators import force_iterable, iter_cause, iter_stack
|
|
27
28
|
from .utils.obj import (
|
|
28
29
|
get_code,
|
|
29
30
|
get_infos,
|
|
@@ -32,7 +33,6 @@ from .utils.obj import (
|
|
|
32
33
|
safe_repr,
|
|
33
34
|
sync_locals,
|
|
34
35
|
)
|
|
35
|
-
from .errors import SetFrameError
|
|
36
36
|
|
|
37
37
|
try:
|
|
38
38
|
from cutter import cut
|
|
@@ -59,14 +59,14 @@ def get_title(frame, event, arg):
|
|
|
59
59
|
return "???"
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def get_frame(frame, key):
|
|
62
|
+
def get_frame(frame, key, tb=None):
|
|
63
63
|
if not key:
|
|
64
64
|
return frame
|
|
65
65
|
|
|
66
|
-
for f in
|
|
66
|
+
for f, _lno in iter_stack(frame, tb):
|
|
67
67
|
if id(f) == key:
|
|
68
68
|
return f
|
|
69
|
-
log.
|
|
69
|
+
log.warning(f"Frame {key} not found")
|
|
70
70
|
return frame
|
|
71
71
|
|
|
72
72
|
|
|
@@ -89,7 +89,11 @@ def serialize_frames(current_frame, current_tb):
|
|
|
89
89
|
line = linecache.getline(filename, lno, frame.f_globals)
|
|
90
90
|
line = line and line.strip()
|
|
91
91
|
startlnos = dis.findlinestarts(code)
|
|
92
|
-
|
|
92
|
+
lnos = list(startlnos)
|
|
93
|
+
lastlineno = None
|
|
94
|
+
if lnos:
|
|
95
|
+
lastlineno = lnos[-1][1]
|
|
96
|
+
|
|
93
97
|
yield {
|
|
94
98
|
"key": id(frame),
|
|
95
99
|
"absoluteFilename": str(fn),
|
|
@@ -103,8 +107,42 @@ def serialize_frames(current_frame, current_tb):
|
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
|
|
106
|
-
def
|
|
107
|
-
prompt
|
|
110
|
+
def serialize_answer_recursive(prompt, frame):
|
|
111
|
+
return serialize_answer(prompt, frame, True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _rec_exec(code, globals, locals):
|
|
115
|
+
from .stepping import start_trace, steppings, stop_trace
|
|
116
|
+
from .utils import current_origin
|
|
117
|
+
|
|
118
|
+
frame = sys._getframe()
|
|
119
|
+
stop_trace(frame)
|
|
120
|
+
|
|
121
|
+
origin = current_origin()
|
|
122
|
+
steppings[origin] = {
|
|
123
|
+
"type": "stepInto",
|
|
124
|
+
"frame": frame,
|
|
125
|
+
"lno": frame.f_lineno,
|
|
126
|
+
"skip_frames": 1, # Skip exec frame
|
|
127
|
+
"_parent": steppings.get(origin),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
start_trace(frame)
|
|
131
|
+
try:
|
|
132
|
+
exec(code, globals, locals)
|
|
133
|
+
finally:
|
|
134
|
+
parent = steppings[origin].get("_parent")
|
|
135
|
+
if parent:
|
|
136
|
+
steppings[origin] = parent
|
|
137
|
+
else:
|
|
138
|
+
stop_trace(frame)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def rec_exec(code, globals, locals):
|
|
142
|
+
sys.call_tracing(_rec_exec, (code, globals, locals))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def serialize_answer(prompt, frame, recursive=False):
|
|
108
146
|
duration = 0
|
|
109
147
|
answer = []
|
|
110
148
|
f_globals = dict(frame.f_globals)
|
|
@@ -117,16 +155,16 @@ def serialize_answer(prompt, frame):
|
|
|
117
155
|
last_key = "_" if "_" not in f_locals else "__"
|
|
118
156
|
f_locals[last_key] = frame.f_globals["__kalong_last_value__"]
|
|
119
157
|
|
|
120
|
-
with capture_exception(answer), capture_display(
|
|
121
|
-
answer
|
|
122
|
-
) as out, capture_std(answer):
|
|
158
|
+
with capture_exception(answer), capture_display(answer) as out, capture_std(answer):
|
|
123
159
|
compiled_code = None
|
|
124
160
|
try:
|
|
125
|
-
compiled_code = compile(prompt, "<stdin>", "single")
|
|
161
|
+
compiled_code = compile(prompt.strip(), "<stdin>", "single")
|
|
162
|
+
prompt = prompt.strip()
|
|
126
163
|
except SetFrameError:
|
|
127
164
|
raise
|
|
128
165
|
except Exception:
|
|
129
166
|
try:
|
|
167
|
+
prompt = dedent(prompt)
|
|
130
168
|
compiled_code = compile(prompt, "<stdin>", "exec")
|
|
131
169
|
except SetFrameError:
|
|
132
170
|
raise
|
|
@@ -137,29 +175,36 @@ def serialize_answer(prompt, frame):
|
|
|
137
175
|
start = time.time()
|
|
138
176
|
if compiled_code is not None:
|
|
139
177
|
try:
|
|
140
|
-
exec(compiled_code, f_globals, f_locals)
|
|
178
|
+
(rec_exec if recursive else exec)(compiled_code, f_globals, f_locals)
|
|
141
179
|
except SetFrameError:
|
|
142
180
|
raise
|
|
143
181
|
except Exception:
|
|
144
182
|
# handle ex
|
|
145
183
|
sys.excepthook(*sys.exc_info())
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if
|
|
184
|
+
if "__kalong_current_frame__" in f_locals:
|
|
185
|
+
del f_locals["__kalong_current_frame__"]
|
|
186
|
+
if "cut" in f_locals:
|
|
187
|
+
del f_locals["cut"]
|
|
188
|
+
if last_key and last_key in f_locals:
|
|
149
189
|
del f_locals[last_key]
|
|
150
190
|
sync_locals(frame, f_locals)
|
|
151
|
-
if out.obj:
|
|
191
|
+
if out.obj is not None:
|
|
152
192
|
frame.f_globals["__kalong_last_value__"] = out.obj
|
|
153
193
|
duration = int((time.time() - start) * 1000 * 1000 * 1000)
|
|
154
194
|
|
|
155
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
"prompt": prompt,
|
|
197
|
+
"answer": answer,
|
|
198
|
+
"duration": duration,
|
|
199
|
+
"recursive": recursive,
|
|
200
|
+
}
|
|
156
201
|
|
|
157
202
|
|
|
158
|
-
def serialize_exception(type_, value, tb):
|
|
203
|
+
def serialize_exception(type_, value, tb, subtype="root"):
|
|
159
204
|
return {
|
|
160
205
|
"type": "exception",
|
|
161
206
|
"id": obj_cache.register(value),
|
|
162
|
-
"subtype":
|
|
207
|
+
"subtype": subtype,
|
|
163
208
|
"name": type_.__name__,
|
|
164
209
|
"description": str(value),
|
|
165
210
|
"traceback": list(serialize_frames(None, tb)),
|
|
@@ -358,8 +403,12 @@ def serialize_suggestion(prompt, from_, to, cursor, frame):
|
|
|
358
403
|
|
|
359
404
|
|
|
360
405
|
def serialize_table(prompt, frame):
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
if table_separator in prompt:
|
|
407
|
+
valueStr, columns = (s.strip() for s in prompt.split(table_separator))
|
|
408
|
+
columns = [c.strip() for c in columns.split(",")]
|
|
409
|
+
else:
|
|
410
|
+
valueStr = prompt.strip()
|
|
411
|
+
columns = None
|
|
363
412
|
|
|
364
413
|
try:
|
|
365
414
|
valueKey = get_id_from_expression(valueStr, frame)
|
|
@@ -370,6 +419,19 @@ def serialize_table(prompt, frame):
|
|
|
370
419
|
}
|
|
371
420
|
|
|
372
421
|
value = obj_cache.get(valueKey)
|
|
422
|
+
values = list(force_iterable(value, True))
|
|
423
|
+
if not columns:
|
|
424
|
+
if all(isinstance(value, dict) for value in values):
|
|
425
|
+
columns = list({column for value in values for column in value.keys()})
|
|
426
|
+
else:
|
|
427
|
+
columns = list(
|
|
428
|
+
{
|
|
429
|
+
column
|
|
430
|
+
for value in values
|
|
431
|
+
for column in dir(value)
|
|
432
|
+
if not column.startswith("__")
|
|
433
|
+
}
|
|
434
|
+
)
|
|
373
435
|
|
|
374
436
|
answer = [
|
|
375
437
|
{
|
|
@@ -386,7 +448,7 @@ def serialize_table(prompt, frame):
|
|
|
386
448
|
for column in columns
|
|
387
449
|
for value in [universal_travel(row, column)]
|
|
388
450
|
}
|
|
389
|
-
for row in
|
|
451
|
+
for row in values
|
|
390
452
|
],
|
|
391
453
|
}
|
|
392
454
|
]
|
kalong/forking.py
CHANGED
|
@@ -15,9 +15,7 @@ def forkserver():
|
|
|
15
15
|
kalong_dir = Path(__file__).parent.parent
|
|
16
16
|
env = dict(os.environ)
|
|
17
17
|
env["PYTHONPATH"] = (
|
|
18
|
-
f'{kalong_dir}:{env["PYTHONPATH"]}'
|
|
19
|
-
if env.get("PYTHONPATH")
|
|
20
|
-
else kalong_dir
|
|
18
|
+
f'{kalong_dir}:{env["PYTHONPATH"]}' if env.get("PYTHONPATH") else kalong_dir
|
|
21
19
|
)
|
|
22
20
|
|
|
23
21
|
popen_args = (
|