textual-reflect 0.1.1__py3-none-any.whl → 0.1.6__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.
- textual_reflect/__init__.py +1 -1
- textual_reflect/__main__.py +31 -1
- textual_reflect/reflect.py +124 -86
- textual_reflect/reflect.tcss +28 -0
- {textual_reflect-0.1.1.dist-info → textual_reflect-0.1.6.dist-info}/METADATA +12 -1
- textual_reflect-0.1.6.dist-info/RECORD +8 -0
- textual_reflect-0.1.1.dist-info/RECORD +0 -7
- {textual_reflect-0.1.1.dist-info → textual_reflect-0.1.6.dist-info}/WHEEL +0 -0
- {textual_reflect-0.1.1.dist-info → textual_reflect-0.1.6.dist-info}/licenses/LICENSE +0 -0
textual_reflect/__init__.py
CHANGED
textual_reflect/__main__.py
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
"""Entry point for running the Textual ReflectorApp as a module."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
if __name__ == "__main__":
|
|
4
|
+
from reflect import Reflector
|
|
5
|
+
else:
|
|
6
|
+
from .reflect import Reflector
|
|
7
|
+
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.widgets import Footer, Header
|
|
10
|
+
|
|
11
|
+
class ReflectorApp(App):
|
|
12
|
+
BINDINGS = [("ctrl+t", "toggle_theme", "toggle theme")]
|
|
13
|
+
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
self.header = Header(id="header", icon="🐍")
|
|
16
|
+
self.reflector = Reflector(id="reflector")
|
|
17
|
+
self.footer = Footer(id="footer")
|
|
18
|
+
|
|
19
|
+
yield self.header
|
|
20
|
+
yield self.reflector
|
|
21
|
+
yield self.footer
|
|
22
|
+
|
|
23
|
+
def on_mount(self) -> None:
|
|
24
|
+
self.title = "Reflector"
|
|
25
|
+
self.sub_title = "Demo"
|
|
26
|
+
self.theme = "monokai"
|
|
27
|
+
self.reflector.input.theme = "monokai"
|
|
28
|
+
|
|
29
|
+
def action_toggle_theme(self) -> None:
|
|
30
|
+
themes = ["dracula", "monokai"]
|
|
31
|
+
theme = "dracula" if self.theme == "monokai" else "monokai"
|
|
32
|
+
self.theme = theme
|
|
33
|
+
self.reflector.input.theme = theme
|
|
4
34
|
|
|
5
35
|
if __name__ == "__main__":
|
|
6
36
|
app = ReflectorApp()
|
textual_reflect/reflect.py
CHANGED
|
@@ -1,43 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
3
6
|
import sys
|
|
4
|
-
from code import InteractiveConsole
|
|
7
|
+
from code import interact, InteractiveConsole
|
|
5
8
|
from io import StringIO
|
|
6
|
-
from
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
7
10
|
|
|
8
11
|
from rich.syntax import Syntax
|
|
9
|
-
from textual.app import
|
|
12
|
+
from textual.app import ComposeResult
|
|
10
13
|
from textual.containers import Container, Horizontal
|
|
11
14
|
from textual.widget import Widget
|
|
12
|
-
from textual.widgets import
|
|
15
|
+
from textual.widgets import RichLog, TextArea
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from typing_extensions import Self
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
class Reflector(Widget):
|
|
22
|
+
# def __init__(self):
|
|
23
|
+
# pass
|
|
24
|
+
|
|
16
25
|
BINDINGS = [
|
|
17
26
|
("ctrl+r", "eval", "eval"),
|
|
18
27
|
("ctrl+n", "dir", "namespace"),
|
|
19
28
|
("ctrl+l", "clear_output", "clear output"),
|
|
20
29
|
("ctrl+s", "clear_input", "clear input"),
|
|
21
30
|
]
|
|
31
|
+
|
|
32
|
+
DEFAULT_CSS = """
|
|
33
|
+
#reflector-input {
|
|
34
|
+
padding: 0 1 0 1;
|
|
35
|
+
border: none;
|
|
36
|
+
background: $surface;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#reflector-output {
|
|
40
|
+
padding: 0 1 0 1;
|
|
41
|
+
background: $surface;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#reflector-input-container {
|
|
45
|
+
border: solid $primary;
|
|
46
|
+
height: 0.4fr;
|
|
47
|
+
margin: 0 1 0 1;
|
|
48
|
+
background: $background;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#reflector-input-container:focus-within {
|
|
52
|
+
border: solid $accent;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#reflector-output-container {
|
|
56
|
+
border: solid $primary;
|
|
57
|
+
margin: 0 1 0 1;
|
|
58
|
+
height: 0.6fr;
|
|
59
|
+
background: $background;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#reflector-container {
|
|
63
|
+
border: solid $primary;
|
|
64
|
+
background: $background;
|
|
65
|
+
margin: 0 1 0 1;
|
|
66
|
+
}
|
|
67
|
+
"""
|
|
68
|
+
|
|
22
69
|
|
|
23
|
-
def compose(self) -> ComposeResult:
|
|
70
|
+
def compose(self) -> ComposeResult:
|
|
24
71
|
self.input = TextArea.code_editor(
|
|
25
72
|
id="reflector-input",
|
|
26
73
|
language="python",
|
|
27
|
-
|
|
74
|
+
show_line_numbers=False,
|
|
75
|
+
soft_wrap=True,
|
|
76
|
+
placeholder="Press ^r to evaluate.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self.input_container = Container(
|
|
80
|
+
self.input,
|
|
81
|
+
id="reflector-input-container"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.output = RichLog(
|
|
85
|
+
id="reflector-output",
|
|
86
|
+
markup=True,
|
|
87
|
+
highlight=True,
|
|
88
|
+
# min_width=80,
|
|
89
|
+
# wrap=True
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.output_container = Container(
|
|
93
|
+
self.output,
|
|
94
|
+
id="reflector-output-container"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.container = Container(
|
|
98
|
+
self.output_container,
|
|
99
|
+
self.input_container,
|
|
100
|
+
id="reflector-container"
|
|
28
101
|
)
|
|
29
|
-
self.input_container = Container(self.input, id="reflector-input-container")
|
|
30
|
-
self.output = RichLog(id="reflector-output", markup=True, highlight=True)
|
|
31
|
-
self.output_container = Container(self.output, id="reflector-output-container")
|
|
32
102
|
|
|
33
|
-
yield self.
|
|
34
|
-
|
|
103
|
+
yield self.container
|
|
104
|
+
|
|
35
105
|
|
|
36
106
|
def on_mount(self) -> None:
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
107
|
+
self.stdout, self.stderr = sys.stdout, sys.stderr
|
|
108
|
+
self.more_input = False
|
|
109
|
+
self.prompt = ">>> "
|
|
110
|
+
self.input_container.border_title = f"{self.prompt}"
|
|
111
|
+
self.output_container.border_title = f"{self.app.title}"
|
|
112
|
+
self.input_container.border_subtitle = "Input"
|
|
113
|
+
self.output_container.border_subtitle = "Output"
|
|
39
114
|
self.namespace = {"app": self.app, "__builtins__": __builtins__}
|
|
40
115
|
self.repl = InteractiveConsole(locals=self.namespace)
|
|
116
|
+
self.banner = f"""\
|
|
117
|
+
Python {sys.version} on {sys.platform}
|
|
118
|
+
Type "help", "copyright", "credits" or "license" for more information.
|
|
119
|
+
"""
|
|
120
|
+
self.write(self.banner)
|
|
41
121
|
self.input.focus()
|
|
42
122
|
|
|
43
123
|
def action_dir(self) -> None:
|
|
@@ -49,86 +129,44 @@ class Reflector(Widget):
|
|
|
49
129
|
def action_clear_input(self) -> None:
|
|
50
130
|
self.input.clear()
|
|
51
131
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
code = self.input.text
|
|
55
|
-
if not code:
|
|
56
|
-
return
|
|
132
|
+
def write(self, content:str="") -> Self:
|
|
133
|
+
return self.output.write(Syntax(content, "python", indent_guides=True))
|
|
57
134
|
|
|
58
|
-
|
|
59
|
-
self.output.write(Syntax(f">>> {split_code[0]}", "python", indent_guides=True))
|
|
60
|
-
|
|
61
|
-
if len(split_code) > 1:
|
|
62
|
-
for line in split_code[1:]:
|
|
63
|
-
self.output.write(Syntax(f"... {line}", "python", indent_guides=True))
|
|
64
|
-
self.output.write(Syntax("... ", "python", indent_guides=True))
|
|
65
|
-
|
|
66
|
-
old_stdout, old_stderr = sys.stdout, sys.stderr
|
|
135
|
+
def redirect_io(self):
|
|
67
136
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
|
68
|
-
self.repl.push(code + "\n")
|
|
69
|
-
captured_output = sys.stdout.getvalue().strip()
|
|
70
|
-
captured_error = sys.stderr.getvalue().strip()
|
|
71
137
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.input.clear()
|
|
79
|
-
self.input.focus()
|
|
138
|
+
def restore_io(self):
|
|
139
|
+
sys.stdout, sys.stderr = self.stdout, self.stderr
|
|
140
|
+
|
|
141
|
+
def action_eval(self, code="", capture=False) -> Tuple[str, str]|None:
|
|
142
|
+
if not code:
|
|
143
|
+
code = self.input.text
|
|
80
144
|
|
|
145
|
+
for line in code.split("\n"):
|
|
146
|
+
self.write(f"{self.prompt}{line}")
|
|
147
|
+
self.redirect_io()
|
|
148
|
+
self.more_input = self.repl.push(line)
|
|
149
|
+
captured_output = sys.stdout.getvalue().strip()
|
|
150
|
+
captured_error = sys.stderr.getvalue().strip()
|
|
151
|
+
self.restore_io()
|
|
81
152
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#reflector-input {
|
|
85
|
-
padding: 1 2 1 2;
|
|
86
|
-
border: none;
|
|
87
|
-
background: $surface;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#reflector-output {
|
|
91
|
-
padding: 1 2 1 2;
|
|
92
|
-
background: $panel;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#reflector-input-container {
|
|
96
|
-
border: solid $primary;
|
|
97
|
-
height: 0.4fr;
|
|
98
|
-
margin: 0 2 1 2;
|
|
99
|
-
background: $background;
|
|
100
|
-
}
|
|
153
|
+
if captured_output:
|
|
154
|
+
self.write(captured_output)
|
|
101
155
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#reflector-output-container {
|
|
107
|
-
border: solid $primary;
|
|
108
|
-
margin: 1 2 1 2;
|
|
109
|
-
height: 0.6fr;
|
|
110
|
-
background: $background;
|
|
111
|
-
}
|
|
112
|
-
"""
|
|
156
|
+
if captured_error:
|
|
157
|
+
self.write(captured_error)
|
|
113
158
|
|
|
114
|
-
|
|
159
|
+
if self.more_input:
|
|
160
|
+
self.prompt = "... "
|
|
161
|
+
# self.input_container.styles.border = ("solid", "yellow")
|
|
162
|
+
else:
|
|
163
|
+
self.prompt = ">>> "
|
|
164
|
+
# self.input_container.styles.border = self.accent
|
|
115
165
|
|
|
116
|
-
|
|
117
|
-
self.header = Header(id="header", icon="🐍")
|
|
118
|
-
self.reflector = Reflector(id="reflector")
|
|
119
|
-
self.footer = Footer(id="footer")
|
|
166
|
+
self.input_container.border_title = f"{self.prompt}"
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
yield self.footer
|
|
168
|
+
self.input.clear()
|
|
169
|
+
self.input.focus()
|
|
124
170
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
self.theme = "monokai"
|
|
128
|
-
self.reflector.input.theme = "monokai"
|
|
129
|
-
|
|
130
|
-
def action_toggle_theme(self) -> None:
|
|
131
|
-
themes = ["dracula", "monokai"]
|
|
132
|
-
theme = "dracula" if self.theme == "monokai" else "monokai"
|
|
133
|
-
self.theme = theme
|
|
134
|
-
self.reflector.input.theme = theme
|
|
171
|
+
if capture:
|
|
172
|
+
return captured_output, captured_error
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#reflector-input {
|
|
2
|
+
padding: 1 2 1 2;
|
|
3
|
+
border: none;
|
|
4
|
+
background: $surface;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
#reflector-output {
|
|
8
|
+
padding: 1 2 1 2;
|
|
9
|
+
background: $panel;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#reflector-input-container {
|
|
13
|
+
border: solid $primary;
|
|
14
|
+
height: 0.4fr;
|
|
15
|
+
margin: 0 2 1 2;
|
|
16
|
+
background: $background;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#reflector-input-container:focus-within {
|
|
20
|
+
border: solid $accent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#reflector-output-container {
|
|
24
|
+
border: solid $primary;
|
|
25
|
+
margin: 1 2 1 2;
|
|
26
|
+
height: 0.6fr;
|
|
27
|
+
background: $background;
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: textual-reflect
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: A Textual widget for code reflection and introspection.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Ismael-VC/textual-reflect
|
|
6
6
|
Project-URL: Repository, https://github.com/Ismael-VC/textual-reflect.git
|
|
@@ -44,3 +44,14 @@ if __name__ == "__main__":
|
|
|
44
44
|
app = MyApp()
|
|
45
45
|
app.run()
|
|
46
46
|
```
|
|
47
|
+
|
|
48
|
+
TODO
|
|
49
|
+
|
|
50
|
+
- input accept empty string, write empty line, push \r
|
|
51
|
+
- use sys prompts and return value
|
|
52
|
+
- capture banner
|
|
53
|
+
- 1 big container
|
|
54
|
+
- force vertical
|
|
55
|
+
- vertical group?
|
|
56
|
+
- animation top down
|
|
57
|
+
- log not focusable
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
textual_reflect/__init__.py,sha256=ex3-Ib9mxRdvC8B8olg5S7ly3nK1dBf675WCOL7POrg,87
|
|
2
|
+
textual_reflect/__main__.py,sha256=3AHjPr0aTEtp4vGfCkKWyvP5QPz1LZ9wrZxw8p80wUU,1080
|
|
3
|
+
textual_reflect/reflect.py,sha256=iPJcMbZCSLkWrC30vqx-QD2yh1AqjI3ZQ2QkZh6-SrY,4819
|
|
4
|
+
textual_reflect/reflect.tcss,sha256=5iidcsmgFDSnblOTh29wWVTwuFgMCC0oSQuBcRiK3OA,487
|
|
5
|
+
textual_reflect-0.1.6.dist-info/METADATA,sha256=HhVh4UIt5JCq5pDvwQ4xy-eISg9mbyuRf8IBw459mSM,1475
|
|
6
|
+
textual_reflect-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
textual_reflect-0.1.6.dist-info/licenses/LICENSE,sha256=-f4_jKU5jZQN4f13AVWYr07_FjbWLmSXVKLe72_jaGk,1089
|
|
8
|
+
textual_reflect-0.1.6.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
textual_reflect/__init__.py,sha256=0JUnTC0BZ2fzNDpyQ5nwD65r0iETbU_olIu-vXrPLwg,87
|
|
2
|
-
textual_reflect/__main__.py,sha256=1bCP4V0glYB-Fn7mBq1Pp12pkDQVoaFpE1dYvWuLvO4,170
|
|
3
|
-
textual_reflect/reflect.py,sha256=FHk2iFM3hxWrq3oTjvix3bLC2UqONnUaocrkMoutyNc,4085
|
|
4
|
-
textual_reflect-0.1.1.dist-info/METADATA,sha256=dJ726z6fu9xY1mVB4GWM4AuML5Y7M-fGHX6r4SNC32Q,1267
|
|
5
|
-
textual_reflect-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
textual_reflect-0.1.1.dist-info/licenses/LICENSE,sha256=-f4_jKU5jZQN4f13AVWYr07_FjbWLmSXVKLe72_jaGk,1089
|
|
7
|
-
textual_reflect-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|