Aria2TUI 0.1.5.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.
aria2tui/__init__.py ADDED
File without changes
@@ -0,0 +1,289 @@
1
+ #!/bin/python
2
+
3
+ import os
4
+ import sys
5
+ from sys import exit
6
+ import tempfile
7
+ import time
8
+ import toml
9
+ import json
10
+ import curses
11
+
12
+ os.chdir(os.path.dirname(os.path.realpath(__file__)))
13
+ os.chdir("../..")
14
+ sys.path.append(os.path.expanduser("../../"))
15
+
16
+ # from list_picker.ui.list_picker_colours import get_colours, get_help_colours, get_notification_colours
17
+ # from list_picker.utils.options_selectors import default_option_input, output_file_option_selector, default_option_selector
18
+ # from list_picker.utils.table_to_list_of_lists import *
19
+ # from list_picker.utils.utils import *
20
+ # from list_picker.utils.sorting import *
21
+ # from list_picker.utils.filtering import *
22
+ # from list_picker.ui.input_field import *
23
+ # from list_picker.utils.clipboard_operations import *
24
+ # from list_picker.utils.searching import search
25
+ # from list_picker.ui.help_screen import help_lines
26
+ # from list_picker.ui.keys import list_picker_keys, notification_keys, options_keys, menu_keys
27
+ # from list_picker.utils.generate_data import generate_list_picker_data
28
+ # from list_picker.utils.dump import dump_state, load_state, dump_data
29
+ # from list_picker.list_picker_app import Picker, start_curses, close_curses
30
+ from list_picker.list_picker_app import *
31
+
32
+ from aria2tui.lib.aria2c_wrapper import *
33
+ from aria2tui.utils.aria2c_utils import *
34
+ from aria2tui.ui.aria2_detailing import highlights, menu_highlights, modes, operations_highlights
35
+ from aria2tui.ui.aria2tui_keys import download_option_keys
36
+ from aria2tui.graphing.speed_graph import graph_speeds, graph_speeds_gid
37
+ from aria2tui.ui.aria2tui_menu_options import menu_options, download_options, Option, menu_data, downloads_data, dl_operations_data
38
+
39
+
40
+ class Aria2TUI:
41
+ def __init__(
42
+ self,
43
+ stdscr: curses.window,
44
+ download_options: list[Option],
45
+ menu_options: list[Option],
46
+ menu_data: dict,
47
+ downloads_data: dict,
48
+ dl_operations_data: dict,
49
+ ):
50
+ self.stdscr = stdscr
51
+ self.download_options = download_options
52
+ self.menu_options = menu_options
53
+ self.menu_data = menu_data
54
+ self.downloads_data = downloads_data
55
+ self.dl_operations_data = dl_operations_data
56
+ self.add_graph_to_options()
57
+ self.add_require_option_to_dl_operations()
58
+
59
+
60
+ def add_require_option_to_dl_operations(self) -> None:
61
+ self.dl_operations_data["require_option"] = [False if option.name != "Change Position" else True for option in self.download_options]
62
+ self.dl_operations_data["option_functions"] = [None if option.name != "Change Position" else lambda stdscr, refresh_screen_function: default_option_selector(stdscr, field_name="Download Position") for option in self.download_options]
63
+
64
+ def add_graph_to_options(self) -> None:
65
+ """ Add the transmission speed graphs to the menu options. """
66
+ self.download_options.append(
67
+ Option("Transfer Speed Graph *experimental*", graph_speeds_gid, {
68
+ "stdscr": self.stdscr,
69
+ "get_data_function": lambda gid: sendReq(tellStatus(gid)),
70
+
71
+ "graph_wh" : lambda: (
72
+ 9*os.get_terminal_size()[0]//10,
73
+ 9*os.get_terminal_size()[1]//10,
74
+ ),
75
+ "timeout": 1000,
76
+
77
+ "xposf" : lambda: os.get_terminal_size()[0]//20,
78
+ "yposf" : lambda: os.get_terminal_size()[1]//20,
79
+ "title": "Download Transfer Speeds",
80
+ }),
81
+ )
82
+
83
+ self.menu_options.append(
84
+ Option("Transfer Speed Graph *experimental*", graph_speeds, {
85
+ "stdscr": self.stdscr,
86
+ "get_data_function": lambda: sendReq(getGlobalStat()),
87
+ "graph_wh" : lambda: (
88
+ 9*os.get_terminal_size()[0]//10,
89
+ 9*os.get_terminal_size()[1]//10,
90
+ ),
91
+ "xposf" : lambda: os.get_terminal_size()[0]//20,
92
+ "yposf" : lambda: os.get_terminal_size()[1]//20,
93
+ "title": "Global Transfer Speeds",
94
+ }),
95
+ )
96
+
97
+ def run(self) -> None:
98
+ """ Run Aria2TUI app loop. """
99
+ while True:
100
+ ## SELECT DOWNLOADS
101
+ DownloadsPicker = Picker(self.stdscr, **self.downloads_data)
102
+ selected_downloads, opts, self.downloads_data = DownloadsPicker.run()
103
+
104
+ if selected_downloads:
105
+ operation_names = [option.name for option in self.download_options]
106
+ self.dl_operations_data["items"] = operation_names
107
+
108
+ items, header = self.downloads_data["items"], self.downloads_data["header"]
109
+ gid_index, fname_index = header.index("GID"), header.index("Name")
110
+ gids = [item[gid_index] for i, item in enumerate(items) if i in selected_downloads]
111
+ fnames = [item[fname_index] for i, item in enumerate(items) if i in selected_downloads]
112
+
113
+ # Display the download names in an infobox when selecting which operation to perform
114
+ self.dl_operations_data["display_infobox"] = True
115
+ self.dl_operations_data["infobox_items"] = fnames
116
+ self.dl_operations_data["infobox_title"] = f"{len(fnames)} Selected..."
117
+
118
+ ## SELECT OPERATION TO APPLY TO SELECTED DOWNLOADS
119
+ DownloadOperationPicker = Picker(self.stdscr, **self.dl_operations_data)
120
+ selected_operation, opts, self.dl_operations_data = DownloadOperationPicker.run()
121
+ if selected_operation:
122
+ operation = download_options[selected_operation[0]]
123
+
124
+ user_opts = self.dl_operations_data["user_opts"]
125
+ view = False
126
+ if operation.meta_args and "view" in operation.meta_args and operation.meta_args["view"]: view=True
127
+ picker_view = False
128
+ if operation.meta_args and "picker_view" in operation.meta_args and operation.meta_args["picker_view"]: picker_view=True
129
+
130
+
131
+ ## APPLY THE SELECTED OPERATION TO THE SELECTED DOWNLOADS
132
+ applyToDownloads(self.stdscr, gids, operation.name, operation.function, operation.function_args, user_opts, view, fnames=fnames, picker_view=picker_view)
133
+ self.downloads_data["selections"] = {}
134
+ self.dl_operations_data["user_opts"] = ""
135
+ else: continue
136
+
137
+ else:
138
+
139
+ self.menu_data["items"] = [menu_option.name for menu_option in self.menu_options]
140
+ while True:
141
+ ## SELECT MENU OPTION
142
+ MenuPicker = Picker(self.stdscr, **self.menu_data)
143
+ selected_menu, opts, self.menu_data = MenuPicker.run()
144
+
145
+ # If we exit from the menu then exit altogether
146
+ if not selected_menu:
147
+ close_curses(self.stdscr)
148
+ return
149
+
150
+ menu_option = self.menu_options[selected_menu[0]]
151
+ if menu_option.name == "View Downloads":
152
+ self.downloads_data["auto_refresh"] = False
153
+ break
154
+ elif menu_option.name == "Watch Downloads":
155
+ self.downloads_data["auto_refresh"] = True
156
+ break
157
+
158
+ ## if it is a view operation such as "View Global Stats" then send the request and open it with nvim
159
+ elif "view" in menu_option.meta_args and menu_option.meta_args["view"]:
160
+ # Ensure that the screen is cleared after nvim closes, otherwise artifcats remain.
161
+ self.downloads_data["clear_on_start"] = True
162
+ self.menu_data["clear_on_start"] = True
163
+ response = sendReq(menu_option.function(**menu_option.function_args))
164
+ with tempfile.NamedTemporaryFile(delete=False, mode='w') as tmpfile:
165
+ tmpfile.write(json.dumps(response, indent=4))
166
+ tmpfile_path = tmpfile.name
167
+ # cmd = r"""nvim -i NONE -c 'setlocal bt=nofile' -c 'silent! %s/^\s*"function"/\0' -c 'norm ggn'""" + f" {tmpfile_path}"
168
+ cmd = f"nvim {tmpfile_path}"
169
+ process = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE)
170
+ elif "picker_view" in menu_option.meta_args and menu_option.meta_args["picker_view"]:
171
+ self.downloads_data["clear_on_start"] = True
172
+ self.menu_data["clear_on_start"] = True
173
+ response = sendReq(menu_option.function(**menu_option.function_args))
174
+ response = flatten_data(response)
175
+ resp_list = [[key, val] for key, val in response.items()]
176
+ x = Picker(
177
+ self.stdscr,
178
+ items = resp_list,
179
+ header = ["Key", "Val"],
180
+ title=menu_option.name,
181
+ )
182
+ x.run()
183
+ else:
184
+ if "display_message" in menu_option.meta_args and menu_option.meta_args["display_message"]:
185
+ display_message(self.stdscr, menu_option.meta_args["display_message"])
186
+ return_val = menu_option.function(**menu_option.function_args)
187
+
188
+ # Add notification of success or failure to listpicker
189
+ if return_val not in ["", None, []]:
190
+ self.downloads_data["startup_notification"] = str(return_val)
191
+ self.stdscr.clear()
192
+ self.stdscr.refresh()
193
+ break
194
+
195
+ def display_message(stdscr: curses.window, msg: str) -> None:
196
+ h, w = stdscr.getmaxyx()
197
+ if (h>8 and w >20):
198
+ stdscr.addstr(h//2, (w-len(msg))//2, msg)
199
+ stdscr.refresh()
200
+
201
+
202
+ def handleAriaStartPromt(stdscr):
203
+ ## Check if aria is running
204
+ curses.init_pair(1, 253, 232)
205
+ stdscr.bkgd(' ', curses.color_pair(1)) # Apply background color
206
+ stdscr.refresh()
207
+ config = get_config()
208
+
209
+ colour_theme_number=config["appearance"]["theme"]
210
+ while True:
211
+ connection_up = testConnection()
212
+ can_connect = testAriaConnection()
213
+ if not can_connect:
214
+ if not connection_up:
215
+ header, choices = ["Aria2c Connection Down. Do you want to start it?"], ["Yes", "No"]
216
+ connect_data = {
217
+ "items": choices,
218
+ "title": "Aria2TUI",
219
+ "header": header,
220
+ "max_selected": 1,
221
+ "colour_theme_number": colour_theme_number,
222
+ }
223
+ ConnectionPicker = Picker(stdscr, **connect_data)
224
+
225
+ choice, opts, function_data = ConnectionPicker.run()
226
+
227
+ if choice == [1] or choice == []:
228
+ close_curses(stdscr)
229
+ exit()
230
+
231
+ config = get_config()
232
+
233
+ h, w = stdscr.getmaxyx()
234
+ if (h>8 and w >20):
235
+ s = "Starting Aria2c Now..."
236
+ stdscr.addstr(h//2-1, (w-len(s))//2, s)
237
+ s = f'startupcmds = {config["general"]["startupcmds"]}'
238
+ stdscr.addstr(h//2+1, (w-min(len(s), w))//2, s[:w])
239
+ stdscr.refresh()
240
+
241
+ for cmd in config["general"]["startupcmds"]:
242
+ subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
243
+
244
+ time.sleep(2)
245
+ else:
246
+ h, w = stdscr.getmaxyx()
247
+ if (h>8 and w >20):
248
+ s = "The connection is up but unresponsive..."
249
+ stdscr.addstr(h//2, (w-len(s))//2, s)
250
+ s="Is your token correct in config.toml?"
251
+ stdscr.addstr(h//2+1, (w-len(s))//2, s)
252
+ stdscr.refresh()
253
+ stdscr.timeout(5000)
254
+ stdscr.getch()
255
+ close_curses(stdscr)
256
+ exit()
257
+ else:
258
+ break
259
+
260
+
261
+ def aria2tui() -> None:
262
+ """ Main function """
263
+
264
+ ## Run curses
265
+ stdscr = start_curses()
266
+
267
+ ## Check if aria is running and prompt the user to start it if not
268
+ handleAriaStartPromt(stdscr)
269
+
270
+ app = Aria2TUI(
271
+ stdscr,
272
+ download_options,
273
+ menu_options,
274
+ menu_data,
275
+ downloads_data,
276
+ dl_operations_data,
277
+ )
278
+ app.run()
279
+ # begin(stdscr)
280
+
281
+ ## Clean up curses and clear terminal
282
+ stdscr.clear()
283
+ stdscr.refresh()
284
+ close_curses(stdscr)
285
+ os.system('cls' if os.name == 'nt' else 'clear')
286
+
287
+ if __name__ == "__main__":
288
+ # global menu_options, download_options, menu_data, downloads_data, dl_operations_data
289
+ aria2tui()
File without changes
@@ -0,0 +1,213 @@
1
+ import sys, os
2
+ # sys.path.append(os.path.expanduser("../../../list_picker/"))
3
+ # sys.path.append(os.path.expanduser("../../../aria2tui/"))
4
+ os.chdir(os.path.dirname(os.path.realpath(__file__)))
5
+ os.chdir("../../..")
6
+ from aria2tui.lib.aria2c_wrapper import *
7
+ from aria2tui.utils.aria2c_utils import *
8
+ # from list_picker.list_picker_app import Picker, start_curses, close_curses
9
+ from list_picker import *
10
+ from list_picker.list_picker_app import *
11
+ import time
12
+ import re
13
+ from typing import Callable
14
+
15
+ def escape_ansi(line: str):
16
+ ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
17
+ return ansi_escape.sub('', line)
18
+
19
+ def handle_plotille_not_found(stdscr: curses.window) -> None:
20
+ h, w = stdscr.getmaxyx()
21
+ s = "ModuleNotFoundError: No module named 'plotille'"
22
+ stdscr.addstr(h//2, (w - len(s))//2, s)
23
+ stdscr.refresh()
24
+ stdscr.getch()
25
+
26
+
27
+ def graph_speeds(
28
+ stdscr: curses.window,
29
+ get_data_function: Callable,
30
+ timeout:int=1000,
31
+ title:str="",
32
+ refresh_time: int = 2,
33
+ xposf: Callable = lambda: 0,
34
+ yposf: Callable = lambda: 0,
35
+ graph_wh: Callable[None,Tuple[int, int]] = lambda: os.get_terminal_size(),
36
+
37
+ ) -> None:
38
+ try:
39
+ import plotille as plt
40
+ except:
41
+ handle_plotille_not_found(stdscr)
42
+ return None
43
+
44
+ initial_time = time.time()
45
+ x, y, y2 = [], [], []
46
+ while time.time()-initial_time < timeout:
47
+
48
+
49
+ resp = get_data_function()
50
+ x.append(time.time()-initial_time)
51
+ down = int(resp['result']['downloadSpeed'])
52
+ up = int(resp['result']['uploadSpeed'])
53
+
54
+ y.append(down)
55
+ y2.append(up)
56
+
57
+ fig = plt.Figure()
58
+ fig.plot(x, y)
59
+ fig.plot(x, y2)
60
+
61
+ fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
62
+ fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
63
+ fig.set_y_limits(min_=0)
64
+ fig.set_x_limits(min_=0)
65
+ fig.text([x[-1]], [y[0]], ['Dn'])
66
+ fig.text([x[0]], [y2[0]], ['Up'])
67
+
68
+ width, height = graph_wh()
69
+ fig.width = width - 7
70
+ fig.height = height - 4
71
+ globh, globw = stdscr.getmaxyx()
72
+ xpos, ypos = xposf(), yposf()
73
+ maxw, maxh = globw-xpos-1, globh-ypos
74
+
75
+ stdscr.erase()
76
+
77
+ # Draw title
78
+ stdscr.addstr(ypos, xpos, f"{title:^{min(width,maxw)}}")
79
+
80
+ # Draw graph
81
+ lines = fig.show().split('\n')
82
+ for i, line in enumerate(lines):
83
+ if i > maxh-2: break
84
+ line = escape_ansi(line)
85
+
86
+ stdscr.addstr(i+1+ypos, xpos , line[:min(width,maxw)])
87
+
88
+ # Show the extreme points of the width and also the last printable char
89
+ show_control_chars = False
90
+ if show_control_chars:
91
+ stdscr.addstr(0,0, f"{maxw}, {maxh}")
92
+ stdscr.addstr(ypos, xpos, "*", curses.A_REVERSE)
93
+ stdscr.addstr(ypos+maxh-1, xpos, "*", curses.A_REVERSE)
94
+ stdscr.addstr(ypos, xpos+maxw-1, "*", curses.A_REVERSE)
95
+ stdscr.addstr(ypos+maxh-1, xpos+maxw-2, "*", curses.A_REVERSE)
96
+ stdscr.addstr(ypos+ min(height, maxh-1), xpos, "+", curses.A_REVERSE)
97
+ stdscr.addstr(ypos, xpos+min(maxw-1, width), "+", curses.A_REVERSE)
98
+ stdscr.addstr(ypos+min(height, maxh-1), xpos+min(width, maxw-2), "+", curses.A_REVERSE)
99
+
100
+ stdscr.refresh()
101
+ key = stdscr.getch()
102
+ if key in [3, ord('q')]:
103
+ return None
104
+
105
+ def graph_speeds_gid(
106
+ stdscr: curses.window,
107
+ get_data_function: Callable,
108
+ timeout:int=1000,
109
+ title:str="",
110
+ refresh_time: int = 2,
111
+ xposf: Callable = lambda: 0,
112
+ yposf: Callable = lambda: 0,
113
+ graph_wh: Callable[None,Tuple[int, int]] = lambda: os.get_terminal_size(),
114
+ gid:str = ""
115
+
116
+ ) -> None:
117
+ try:
118
+ import plotille as plt
119
+ except:
120
+ handle_plotille_not_found(stdscr)
121
+ return None
122
+
123
+ initial_time = time.time()
124
+ x, y, y2 = [], [], []
125
+ while time.time()-initial_time < timeout:
126
+
127
+ resp = get_data_function(gid)
128
+ x.append(time.time()-initial_time)
129
+ down = int(resp['result']['downloadSpeed'])
130
+ up = int(resp['result']['uploadSpeed'])
131
+
132
+ y.append(down)
133
+ y2.append(up)
134
+
135
+ fig = plt.Figure()
136
+ fig.plot(x, y)
137
+ fig.plot(x, y2)
138
+
139
+ fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
140
+ fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
141
+ fig.set_y_limits(min_=0)
142
+ fig.set_x_limits(min_=0)
143
+ fig.text([x[-1]], [y[0]], ['Dn'])
144
+ fig.text([x[0]], [y2[0]], ['Up'])
145
+
146
+ width, height = graph_wh()
147
+ fig.width = width - 7
148
+ fig.height = height - 4
149
+ globh, globw = stdscr.getmaxyx()
150
+ xpos, ypos = xposf(), yposf()
151
+ maxw, maxh = globw-xpos-1, globh-ypos
152
+
153
+ stdscr.erase()
154
+
155
+ # Draw title
156
+ stdscr.addstr(ypos, xpos, f"{title:^{min(width,maxw)}}")
157
+
158
+ # Draw graph
159
+ lines = fig.show().split('\n')
160
+ for i, line in enumerate(lines):
161
+ if i > maxh-2: break
162
+ line = escape_ansi(line)
163
+
164
+ stdscr.addstr(i+1+ypos, xpos , line[:min(width,maxw)])
165
+
166
+ # Show the extreme points of the width and also the last printable char
167
+ show_control_chars = False
168
+ if show_control_chars:
169
+ stdscr.addstr(0,0, f"{maxw}, {maxh}")
170
+ stdscr.addstr(ypos, xpos, "*", curses.A_REVERSE)
171
+ stdscr.addstr(ypos+maxh-1, xpos, "*", curses.A_REVERSE)
172
+ stdscr.addstr(ypos, xpos+maxw-1, "*", curses.A_REVERSE)
173
+ stdscr.addstr(ypos+maxh-1, xpos+maxw-2, "*", curses.A_REVERSE)
174
+ stdscr.addstr(ypos+ min(height, maxh-1), xpos, "+", curses.A_REVERSE)
175
+ stdscr.addstr(ypos, xpos+min(maxw-1, width), "+", curses.A_REVERSE)
176
+ stdscr.addstr(ypos+min(height, maxh-1), xpos+min(width, maxw-2), "+", curses.A_REVERSE)
177
+
178
+ stdscr.refresh()
179
+ key = stdscr.getch()
180
+ stdscr.refresh()
181
+ if key in [3, ord('q')]:
182
+ return None
183
+ resp = get_data_function(gid)
184
+
185
+
186
+
187
+ if __name__ == "__main__":
188
+ title = "Global Transfer Speeds"
189
+ end_time = 180
190
+ get_data_function = lambda: sendReq(getGlobalStat())
191
+ gid = "60e0c88ed77a24d6"
192
+ get_data_function = lambda: sendReq(tellStatus(gid))
193
+ wait_time = 2
194
+
195
+ stdscr = start_curses()
196
+ size_func = lambda: (
197
+ 3*os.get_terminal_size()[0]//4,
198
+ 3*os.get_terminal_size()[1]//4,
199
+ )
200
+ xposf = lambda: os.get_terminal_size()[0]//8
201
+ yposf = lambda: os.get_terminal_size()[1]//8
202
+ graph_speeds(
203
+ stdscr,
204
+ get_data_function=get_data_function,
205
+ timeout=end_time,
206
+ refresh_time=wait_time,
207
+ title=title,
208
+ graph_wh= size_func,
209
+ xposf=xposf,
210
+ yposf=yposf,
211
+
212
+ )
213
+ close_curses(stdscr)
@@ -0,0 +1,56 @@
1
+ import time
2
+ import os
3
+ import subprocess
4
+ import toml
5
+ from urllib import request as rq
6
+ import json
7
+ import sys
8
+ os.chdir(os.path.dirname(os.path.realpath(__file__)))
9
+ os.chdir("../../..")
10
+ # sys.path.append(os.path.expanduser("../list_picker/"))
11
+ import tempfile
12
+ import tabulate
13
+ from typing import Callable, Tuple
14
+
15
+ from aria2tui.lib.aria2c_wrapper import *
16
+ from aria2tui.utils.aria_adduri import addDownloadFull
17
+ # from list_picker.utils.utils import *
18
+ from list_picker import *
19
+ from list_picker.list_picker_app import *
20
+
21
+ def graph_speeds_no_curses(get_data_function, end_time, title, wait_time):
22
+ ticker = 0
23
+ x, y, y2 = [], [], []
24
+ while ticker < end_time:
25
+ x.append(ticker)
26
+ resp = get_data_function()
27
+ down = int(resp['result']['downloadSpeed'])
28
+ up = int(resp['result']['uploadSpeed'])
29
+ # down = bytes_to_human_readable(resp['result']['downloadSpeed'])
30
+ y.append(down)
31
+ y2.append(up)
32
+ fig = plt.Figure()
33
+ fig.lc = 25
34
+ fig.color_mode = 'byte'
35
+ # fig.lc = 'red'
36
+ fig.bg=100
37
+ fig.plot(x, y, lc=200, label='Download speed')
38
+ fig.plot(x, y2, lc=25, label='Upload speed')
39
+ fig.y_ticks_fkt = lambda y, _: bytes_to_human_readable(y)
40
+ fig.x_ticks_fkt = lambda x, _: f"{int(x)}s"
41
+ fig.set_y_limits(min_=0)
42
+ fig.set_x_limits(min_=0)
43
+ # min(4, max(0, len(y)-1))
44
+ fig.text([x[-1]], [y[0]], ['Down'], lc=200)
45
+ fig.text([x[0]], [y2[0]], ['Up'], lc=25)
46
+ width, height = os.get_terminal_size()
47
+ fig.width = width - 28
48
+ fig.height = height - 8
49
+ os.system('cls' if os.name == 'nt' else 'clear')
50
+ print()
51
+ print(f"{title:^{width}}")
52
+ print()
53
+ print(fig.show())
54
+ # print(plotille.plot(x, y, height=20, width=60))
55
+ time.sleep(wait_time)
56
+ ticker += wait_time
File without changes