-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathawk_tutorial.py
150 lines (130 loc) · 5.25 KB
/
awk_tutorial.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from textual.app import App
from textual.binding import Binding
from textual.containers import VerticalScroll, Vertical
from textual.widgets import Footer, Label, Input
from textual.widgets import Markdown
import subprocess
import os
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent.resolve()
class AwkTutorialApp(App):
ENABLE_COMMAND_PALETTE = False
CSS_PATH = SCRIPT_DIR.joinpath('awk_tutorial.css')
BINDINGS = [
Binding('ctrl+p', 'previous', 'Previous', show=True),
Binding('ctrl+n', 'next', 'Next', show=True),
Binding('ctrl+r', 'reset', 'Reset', show=True),
('ctrl+t', 'toggle_theme', 'Theme'),
('ctrl+q', 'app.quit', 'Quit'),
]
def __init__(self):
super().__init__()
self.idx = 0
self.tutorial_files = sorted(SCRIPT_DIR.joinpath('tutorial').glob('*.md'))
self.max_idx = len(self.tutorial_files) - 1
self.l_title = Label(id='title')
self.v_tutorial = VerticalScroll()
def compose(self):
yield self.l_title
with Vertical() as self.v_main:
yield self.v_tutorial
yield Footer()
def on_mount(self):
self.dark = False
self.setup_tutorial()
def on_input_submitted(self, event):
cnt = int(event.input.name)
self.i_cmd = self.i_cmds[cnt]
self.l_cmd_output = self.l_cmd_outputs[cnt]
self.process_user_cmd()
def process_user_cmd(self):
try:
result = subprocess.run(self.i_cmd.value, timeout=2,
shell=True, capture_output=True, text=True)
except subprocess.TimeoutExpired:
msg = ('App might become unresponsive.\n'
'Wait a few seconds...\n'
'Or, press Ctrl+C to quit (press multiple times if needed).')
self.l_cmd_output.update(msg)
self.l_cmd_output_style('red', 'Oops, command timed out!!!', '')
self.i_cmd.styles.background = 'palevioletred'
else:
if result.returncode:
self.l_cmd_output.update(result.stderr)
self.l_cmd_output_style('red', 'Error!',
f'Exit Status: {result.returncode}')
self.i_cmd.styles.background = 'lightgray'
else:
self.l_cmd_output.update(self.trim(result.stdout))
self.l_cmd_output_style('gray', 'Output', '')
def setup_tutorial(self):
self.v_tutorial.remove()
block = False
s = ''
cnt = 0
self.i_cmds = []
self.l_cmd_outputs = []
tt_widgets = []
with open(self.tutorial_files[self.idx]) as f:
title = f'[b]({self.idx+1}/{self.max_idx+1}) {next(f)[2:-1]}'
self.l_title.update(title)
for line in f:
if line.startswith('```'):
if block:
cb_widgets = []
for c in s.splitlines():
if c.startswith('#'):
cb_widgets.append(Label(c, classes='comment'))
elif c.startswith('$ '):
self.i_cmd = Input(classes='input',
name=str(cnt))
self.l_cmd_output = Label(classes='cmd_output',
markup=False)
cb_widgets.append(self.i_cmd)
cb_widgets.append(self.l_cmd_output)
self.i_cmds.append(self.i_cmd)
self.l_cmd_outputs.append(self.l_cmd_output)
self.set_cmd(c[2:])
if cnt == 0:
self.i_cmd.focus()
cnt += 1
tt_widgets.append(Vertical(*cb_widgets, classes='codeblock'))
else:
tt_widgets.append(Markdown(s))
s = ''
block ^= True
continue
s += line
if s:
tt_widgets.append(Markdown(s))
self.v_tutorial = VerticalScroll(*tt_widgets)
self.v_main.mount(self.v_tutorial)
def set_cmd(self, cmd):
self.i_cmd.value = cmd
self.i_cmd.cursor_position = len(cmd)
self.process_user_cmd()
def trim(self, text):
return text.removesuffix('\n')
def l_cmd_output_style(self, color, title, subtitle):
self.l_cmd_output.styles.color = color
self.l_cmd_output.styles.border = ('round', color)
self.l_cmd_output.border_title = title
self.l_cmd_output.border_subtitle = subtitle
def action_previous(self):
if self.idx > 0:
self.idx -= 1
self.setup_tutorial()
def action_next(self):
if self.idx < self.max_idx:
self.idx += 1
self.setup_tutorial()
def action_reset(self):
self.setup_tutorial()
def action_toggle_theme(self):
self.dark = not self.dark
def main():
os.chdir(SCRIPT_DIR.joinpath('example_files'))
app = AwkTutorialApp()
app.run()
if __name__ == '__main__':
main()