abdocode 1.2.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.
hotkey_engine/core.py ADDED
@@ -0,0 +1,1455 @@
1
+ from tkinter import messagebox, simpledialog
2
+ import tkinter as tk
3
+ import numpy as np
4
+ import sounddevice as sd
5
+ import os
6
+ import platform
7
+ import re
8
+ import string
9
+ import threading
10
+ import time
11
+ import json
12
+ import math
13
+ import statistics
14
+ import random
15
+ import datetime
16
+ import urllib.request
17
+ import urllib.parse
18
+ import urllib.error
19
+ import subprocess
20
+ import shutil
21
+ import logging
22
+ from pathlib import Path
23
+ from collections import defaultdict, deque
24
+
25
+ _used_shortcuts = {}
26
+ _enabled = True
27
+ _clipboard_history = []
28
+ _timer_registry = {}
29
+
30
+
31
+ # =========================
32
+ # CORE SAFETY FUNCTION
33
+ # =========================
34
+
35
+ def _safe_event(root, event):
36
+ widget = root.focus_get()
37
+ if widget:
38
+ try:
39
+ widget.event_generate(event)
40
+ except:
41
+ pass
42
+
43
+
44
+ # =========================
45
+ # BASIC SHORTCUT SYSTEM
46
+ # =========================
47
+
48
+ def add_shortcut(root, key, event):
49
+ if key in _used_shortcuts:
50
+ messagebox.showwarning(
51
+ "Hotkey Warning",
52
+ f"{key} already used for { _used_shortcuts[key] }"
53
+ )
54
+ return False
55
+
56
+ def handler(e):
57
+ if not _enabled:
58
+ return
59
+ _safe_event(root, event)
60
+
61
+ root.bind_all(key, handler)
62
+ _used_shortcuts[key] = event
63
+ return True
64
+
65
+
66
+ def remove_shortcut(root, key):
67
+ if key in _used_shortcuts:
68
+ root.unbind_all(key)
69
+ del _used_shortcuts[key]
70
+ return True
71
+ return False
72
+
73
+
74
+ def disable_all():
75
+ global _enabled
76
+ _enabled = False
77
+
78
+
79
+ def enable_all():
80
+ global _enabled
81
+ _enabled = True
82
+
83
+
84
+ def reset_all(root):
85
+ for key in list(_used_shortcuts.keys()):
86
+ root.unbind_all(key)
87
+ _used_shortcuts.clear()
88
+
89
+
90
+ def list_shortcuts():
91
+ return _used_shortcuts.copy()
92
+ def copy(root): add_shortcut(root, "<Control-c>", "<<Copy>>")
93
+ def paste(root): add_shortcut(root, "<Control-v>", "<<Paste>>")
94
+ def cut(root): add_shortcut(root, "<Control-x>", "<<Cut>>")
95
+ def select_all(root): add_shortcut(root, "<Control-a>", "<<SelectAll>>")
96
+ def undo(root): add_shortcut(root, "<Control-z>", "<<Undo>>")
97
+ def redo(root): add_shortcut(root, "<Control-y>", "<<Redo>>")
98
+ def find(root): add_shortcut(root, "<Control-f>", "<<Find>>")
99
+ def replace(root): add_shortcut(root, "<Control-h>", "<<Replace>>")
100
+ def goto(root): add_shortcut(root, "<Control-g>", "<<Goto>>")
101
+ def duplicate(root): add_shortcut(root, "<Control-d>", "<<Duplicate>>")
102
+ def select_line(root): add_shortcut(root, "<Control-l>", "<<SelectLine>>")
103
+ def delete_word(root): add_shortcut(root, "<Control-BackSpace>", "<<DeleteWord>>")
104
+ def delete_line(root): add_shortcut(root, "<Control-Delete>", "<<DeleteLine>>")
105
+ def cut_alt(root): add_shortcut(root, "<Shift-Delete>", "<<Cut>>")
106
+ def home(root): add_shortcut(root, "<Home>", "<<Home>>")
107
+ def end(root): add_shortcut(root, "<End>", "<<End>>")
108
+ def word_left(root): add_shortcut(root, "<Control-Left>", "<<WordLeft>>")
109
+ def word_right(root): add_shortcut(root, "<Control-Right>", "<<WordRight>>")
110
+ def page_up(root): add_shortcut(root, "<Control-Up>", "<<PageUp>>")
111
+ def page_down(root): add_shortcut(root, "<Control-Down>", "<<PageDown>>")
112
+ def select_left(root): add_shortcut(root, "<Shift-Left>", "<<SelectLeft>>")
113
+ def select_right(root): add_shortcut(root, "<Shift-Right>", "<<SelectRight>>")
114
+ def select_up(root): add_shortcut(root, "<Shift-Up>", "<<SelectUp>>")
115
+ def select_down(root): add_shortcut(root, "<Shift-Down>", "<<SelectDown>>")
116
+ def select_word_left(root): add_shortcut(root, "<Control-Shift-Left>", "<<SelectWordLeft>>")
117
+ def select_word_right(root): add_shortcut(root, "<Control-Shift-Right>", "<<SelectWordRight>>")
118
+ def save(root): add_shortcut(root, "<Control-s>", "<<Save>>")
119
+ def open_file(root): add_shortcut(root, "<Control-o>", "<<Open>>")
120
+ def new_file(root): add_shortcut(root, "<Control-n>", "<<New>>")
121
+ def print_file(root): add_shortcut(root, "<Control-p>", "<<Print>>")
122
+ def refresh(root): add_shortcut(root, "<F5>", "<<Refresh>>")
123
+ def use_all(root):
124
+ copy(root)
125
+ paste(root)
126
+ cut(root)
127
+ select_all(root)
128
+ undo(root)
129
+ redo(root)
130
+ find(root)
131
+ replace(root)
132
+ goto(root)
133
+ duplicate(root)
134
+ select_line(root)
135
+ delete_word(root)
136
+ delete_line(root)
137
+ cut_alt(root)
138
+ home(root)
139
+ end(root)
140
+ word_left(root)
141
+ word_right(root)
142
+ page_up(root)
143
+ page_down(root)
144
+ select_left(root)
145
+ select_right(root)
146
+ select_up(root)
147
+ select_down(root)
148
+ select_word_left(root)
149
+ select_word_right(root)
150
+ save(root)
151
+ open_file(root)
152
+ new_file(root)
153
+ print_file(root)
154
+ refresh(root)
155
+ def show_all_shortcuts():
156
+ shortcuts = list_shortcuts()
157
+ if not shortcuts:
158
+ messagebox.showinfo("Hotkeys", "No shortcuts defined.")
159
+ return
160
+
161
+ msg = "Current Shortcuts:\n\n"
162
+ for key, event in shortcuts.items():
163
+ msg += f"{key} -> {event}\n"
164
+
165
+ messagebox.showinfo("Hotkeys", msg)
166
+ def shortcuts_popup_list(root):
167
+ shortcuts = list_shortcuts()
168
+ if not shortcuts:
169
+ messagebox.showinfo("Hotkeys", "No shortcuts defined.")
170
+ return
171
+
172
+ msg = "Current Shortcuts:\n\n"
173
+ for key, event in shortcuts.items():
174
+ msg += f"{key} -> {event}\n"
175
+
176
+
177
+ shortcuts_list = tk.Listbox(root)
178
+
179
+
180
+ for key, event in shortcuts.items():
181
+ shortcuts_list.insert(tk.END, f"{key} -> {event}")
182
+ root.bind_all("<Button-3>", lambda e: shortcuts_list.tk_popup(e.x_root, e.y_root))
183
+
184
+
185
+ # =========================
186
+ # CLIPBOARD UTILITIES
187
+ # =========================
188
+
189
+ def _add_clipboard_history(text):
190
+ text = str(text)
191
+ if text and (_clipboard_history == [] or _clipboard_history[-1] != text):
192
+ _clipboard_history.append(text)
193
+
194
+
195
+ def write_clipboard(root, text):
196
+ try:
197
+ root.clipboard_clear()
198
+ root.clipboard_append(str(text))
199
+ root.update()
200
+ _add_clipboard_history(text)
201
+ return True
202
+ except Exception:
203
+ return False
204
+
205
+
206
+ def read_clipboard(root):
207
+ try:
208
+ return root.clipboard_get()
209
+ except Exception:
210
+ return ""
211
+
212
+
213
+ def clear_clipboard(root):
214
+ try:
215
+ root.clipboard_clear()
216
+ root.update()
217
+ return True
218
+ except Exception:
219
+ return False
220
+
221
+
222
+ def get_clipboard_history():
223
+ return list(_clipboard_history)
224
+
225
+
226
+ def paste_clipboard_history_item(root, index):
227
+ try:
228
+ text = _clipboard_history[index]
229
+ return write_clipboard(root, text)
230
+ except Exception:
231
+ return False
232
+
233
+
234
+ # =========================
235
+ # FILE UTILITIES
236
+ # =========================
237
+
238
+ def read_text_file(path, encoding='utf-8'):
239
+ path_obj = Path(path)
240
+ with path_obj.open('r', encoding=encoding) as file:
241
+ return file.read()
242
+
243
+
244
+ def write_text_file(path, text, encoding='utf-8'):
245
+ path_obj = Path(path)
246
+ path_obj.parent.mkdir(parents=True, exist_ok=True)
247
+ with path_obj.open('w', encoding=encoding) as file:
248
+ file.write(str(text))
249
+
250
+
251
+ def append_text_file(path, text, encoding='utf-8'):
252
+ path_obj = Path(path)
253
+ path_obj.parent.mkdir(parents=True, exist_ok=True)
254
+ with path_obj.open('a', encoding=encoding) as file:
255
+ file.write(str(text))
256
+
257
+
258
+ def list_files(folder, extension=None, recursive=False):
259
+ base = Path(folder)
260
+ if recursive:
261
+ items = base.rglob('*')
262
+ else:
263
+ items = base.glob('*')
264
+ files = [str(p) for p in items if p.is_file()]
265
+ if extension:
266
+ return [f for f in files if f.lower().endswith(extension.lower())]
267
+ return files
268
+
269
+
270
+ def find_files(folder, pattern, recursive=True, extension=None):
271
+ base = Path(folder)
272
+ matcher = re.compile(pattern)
273
+ paths = base.rglob('*') if recursive else base.glob('*')
274
+ matches = []
275
+ for p in paths:
276
+ if p.is_file():
277
+ if matcher.search(str(p)):
278
+ if extension is None or str(p).lower().endswith(extension.lower()):
279
+ matches.append(str(p))
280
+ return matches
281
+
282
+
283
+ # =========================
284
+ # TEXT UTILITIES
285
+ # =========================
286
+
287
+ def normalize_whitespace(text):
288
+ return re.sub(r'\s+', ' ', str(text)).strip()
289
+
290
+
291
+ def count_words(text):
292
+ return len(normalize_whitespace(text).split(' ')) if normalize_whitespace(text) else 0
293
+
294
+
295
+ def to_title_case(text):
296
+ return str(text).title()
297
+
298
+
299
+ def snake_to_camel(text):
300
+ parts = str(text).split('_')
301
+ return parts[0].lower() + ''.join(word.capitalize() for word in parts[1:])
302
+
303
+
304
+ def camel_to_snake(text):
305
+ s = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', str(text))
306
+ return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s).lower()
307
+
308
+
309
+ def remove_punctuation(text):
310
+ return str(text).translate(str.maketrans('', '', string.punctuation))
311
+
312
+
313
+ # =========================
314
+ # TIMER & SCHEDULER UTILITIES
315
+ # =========================
316
+
317
+ def set_timeout(callback, delay, *args, **kwargs):
318
+ timer = threading.Timer(delay, callback, args=args, kwargs=kwargs)
319
+ timer.daemon = True
320
+ timer.start()
321
+ timer_id = id(timer)
322
+ _timer_registry[timer_id] = timer
323
+ return timer_id
324
+
325
+
326
+ def _interval_runner(timer_id, callback, interval, args, kwargs):
327
+ if timer_id not in _timer_registry:
328
+ return
329
+ callback(*args, **kwargs)
330
+ timer = threading.Timer(interval, _interval_runner, args=(timer_id, callback, interval, args, kwargs))
331
+ timer.daemon = True
332
+ _timer_registry[timer_id] = timer
333
+ timer.start()
334
+
335
+
336
+ def set_interval(callback, interval, *args, **kwargs):
337
+ timer_id = int(time.time() * 1000000)
338
+ timer = threading.Timer(interval, _interval_runner, args=(timer_id, callback, interval, args, kwargs))
339
+ timer.daemon = True
340
+ _timer_registry[timer_id] = timer
341
+ timer.start()
342
+ return timer_id
343
+
344
+
345
+ def cancel_timer(timer_id):
346
+ timer = _timer_registry.pop(timer_id, None)
347
+ if timer:
348
+ timer.cancel()
349
+ return True
350
+ return False
351
+
352
+
353
+ def active_timers():
354
+ return list(_timer_registry.keys())
355
+
356
+
357
+ # =========================
358
+ # SYSTEM UTILITIES
359
+ # =========================
360
+
361
+ def get_system_info():
362
+ try:
363
+ user = os.getlogin()
364
+ except Exception:
365
+ user = None
366
+ return {
367
+ 'platform': platform.system(),
368
+ 'release': platform.release(),
369
+ 'version': platform.version(),
370
+ 'machine': platform.machine(),
371
+ 'processor': platform.processor(),
372
+ 'python_version': platform.python_version(),
373
+ 'cwd': os.getcwd(),
374
+ 'user': user,
375
+ }
376
+
377
+
378
+ def get_environment_variables():
379
+ return dict(os.environ)
380
+
381
+
382
+ def open_folder(path):
383
+ path_obj = Path(path)
384
+ if not path_obj.exists():
385
+ return False
386
+ try:
387
+ if platform.system() == 'Windows':
388
+ os.startfile(str(path_obj))
389
+ elif platform.system() == 'Darwin':
390
+ os.system(f'open "{path_obj}"')
391
+ else:
392
+ os.system(f'xdg-open "{path_obj}"')
393
+ return True
394
+ except Exception:
395
+ return False
396
+
397
+
398
+ def get_file_size(path):
399
+ path_obj = Path(path)
400
+ return path_obj.stat().st_size if path_obj.exists() else 0
401
+
402
+
403
+ # =========================
404
+ # UI CONVENIENCE SHORTCUTS
405
+ # =========================
406
+
407
+ def show_info(title, message):
408
+ messagebox.showinfo(title, str(message))
409
+
410
+
411
+ def show_warning(title, message):
412
+ messagebox.showwarning(title, str(message))
413
+
414
+
415
+ def show_error(title, message):
416
+ messagebox.showerror(title, str(message))
417
+
418
+
419
+ def ask_yes_no(title, message):
420
+ return messagebox.askyesno(title, str(message))
421
+
422
+
423
+ def ask_input(title, prompt, default=''):
424
+ return simpledialog.askstring(title, prompt, initialvalue=default)
425
+
426
+
427
+ def simple_popup(root, title, message):
428
+ popup = tk.Toplevel(root)
429
+ popup.title(title)
430
+ label = tk.Label(popup, text=str(message), padx=10, pady=10)
431
+ label.pack()
432
+ btn = tk.Button(popup, text='OK', command=popup.destroy, padx=8, pady=4)
433
+ btn.pack(pady=10)
434
+ popup.transient(root)
435
+ popup.grab_set()
436
+ root.wait_window(popup)
437
+
438
+
439
+ def note_to_frequency(note):
440
+ note = note.strip()
441
+ if note.upper() == "REST":
442
+ return 0.0
443
+
444
+ octave = int(note[-1])
445
+ name = note[:-1]
446
+ if len(name) > 1 and name[-1] in "#b":
447
+ base_name = name
448
+ else:
449
+ base_name = name
450
+
451
+ semitone_map = {
452
+ 'C': 0, 'C#': 1, 'Db': 1,
453
+ 'D': 2, 'D#': 3, 'Eb': 3,
454
+ 'E': 4, 'F': 5, 'F#': 6,
455
+ 'Gb': 6, 'G': 7, 'G#': 8,
456
+ 'Ab': 8, 'A': 9, 'A#': 10,
457
+ 'Bb': 10, 'B': 11,
458
+ }
459
+
460
+ semitone = semitone_map.get(base_name, 0) + (octave + 1) * 12
461
+ return 440.0 * 2 ** ((semitone - 69) / 12.0)
462
+
463
+
464
+ def play_tone(note, duration=0.2, volume=0.5, waveform='sine'):
465
+ freq = note_to_frequency(note) if isinstance(note, str) else note
466
+ if freq == 0:
467
+ sd.sleep(int(duration * 1000))
468
+ return
469
+
470
+ fs = 44100
471
+ t = np.linspace(0, duration, int(fs * duration), endpoint=False)
472
+
473
+ if waveform == 'square':
474
+ wave = np.sign(np.sin(2 * np.pi * freq * t))
475
+ elif waveform == 'triangle':
476
+ wave = 2 * np.abs(2 * ((freq * t) % 1) - 1) - 1
477
+ else:
478
+ wave = np.sin(2 * np.pi * freq * t)
479
+
480
+ envelope = np.ones_like(wave)
481
+ attack = min(0.02, duration * 0.2)
482
+ release = min(0.03, duration * 0.2)
483
+ attack_len = int(fs * attack)
484
+ release_len = int(fs * release)
485
+ if attack_len > 0:
486
+ envelope[:attack_len] = np.linspace(0.0, 1.0, attack_len)
487
+ if release_len > 0:
488
+ envelope[-release_len:] = np.linspace(1.0, 0.0, release_len)
489
+
490
+ wave = volume * envelope * wave
491
+ sd.play(wave.astype(np.float32), fs)
492
+ sd.wait()
493
+
494
+
495
+ def play_notes(sequence, tempo=120, volume=0.4, waveform='sine'):
496
+ beat = 60.0 / tempo
497
+ for note, length in sequence:
498
+ play_tone(note, duration=length * beat, volume=volume, waveform=waveform)
499
+
500
+
501
+ sound_map = {}
502
+
503
+ sound_map["Victory"] = lambda: play_notes([('C4', 0.25), ('E4', 0.25), ('G4', 0.5), ('C5', 0.3)], tempo=140)
504
+ sound_map["Defeat"] = lambda: play_notes([('E4', 0.3), ('C4', 0.3), ('A3', 0.4)], tempo=90)
505
+ sound_map["Win"] = lambda: play_notes([('G4', 0.2), ('B4', 0.2), ('D5', 0.3)], tempo=150)
506
+ sound_map["Lose"] = lambda: play_notes([('D4', 0.2), ('C4', 0.3), ('A3', 0.4)], tempo=100)
507
+ sound_map["Click"] = lambda: play_notes([('C5', 0.1)], tempo=180)
508
+ sound_map["Hover"] = lambda: play_notes([('E5', 0.08), ('D5', 0.08)], tempo=200)
509
+ sound_map["Select"] = lambda: play_notes([('G4', 0.1), ('B4', 0.1)], tempo=180)
510
+ sound_map["Cancel"] = lambda: play_notes([('D4', 0.2)], tempo=120)
511
+ sound_map["Start"] = lambda: play_notes([('E4', 0.15), ('G4', 0.15), ('C5', 0.25)], tempo=150)
512
+ sound_map["Finish"] = lambda: play_notes([('C5', 0.2), ('E5', 0.2), ('G5', 0.3)], tempo=130)
513
+ sound_map["LevelUp"] = lambda: play_notes([('C4', 0.15), ('E4', 0.15), ('G4', 0.2), ('C5', 0.25)], tempo=170)
514
+ sound_map["LevelDown"] = lambda: play_notes([('G4', 0.2), ('E4', 0.2), ('C4', 0.3)], tempo=100)
515
+ sound_map["Coin"] = lambda: play_notes([('C6', 0.1), ('E6', 0.1)], tempo=180)
516
+ sound_map["Score"] = lambda: play_notes([('G5', 0.15), ('A5', 0.15), ('C6', 0.2)], tempo=160)
517
+ sound_map["Bonus"] = lambda: play_notes([('A4', 0.12), ('C5', 0.12), ('E5', 0.2)], tempo=170)
518
+ sound_map["Combo"] = lambda: play_notes([('B4', 0.1), ('D5', 0.1), ('F#5', 0.2)], tempo=180)
519
+ sound_map["UltraWin"] = lambda: play_notes([('C5', 0.15), ('E5', 0.15), ('G5', 0.15), ('B5', 0.3)], tempo=160)
520
+ sound_map["GameOver"] = lambda: play_notes([('A3', 0.4), ('F3', 0.4)], tempo=70)
521
+ sound_map["Restart"] = lambda: play_notes([('D4', 0.2), ('F#4', 0.2)], tempo=130)
522
+ sound_map["Pause"] = lambda: play_notes([('E4', 0.3), ('D4', 0.3)], tempo=90)
523
+ sound_map["Resume"] = lambda: play_notes([('G4', 0.2), ('A4', 0.2)], tempo=140)
524
+ sound_map["Unlock"] = lambda: play_notes([('C5', 0.2), ('D5', 0.2), ('E5', 0.2)], tempo=140)
525
+ sound_map["Lock"] = lambda: play_notes([('C4', 0.3), ('A3', 0.3)], tempo=80)
526
+ sound_map["Achievement"] = lambda: play_notes([('G4', 0.18), ('B4', 0.18), ('D5', 0.24)], tempo=150)
527
+ sound_map["Boss"] = lambda: play_notes([('C2', 0.4), ('G2', 0.4)], tempo=60, waveform='triangle')
528
+ sound_map["Alert"] = lambda: play_notes([('G5', 0.15), ('E5', 0.15)], tempo=160)
529
+ sound_map["Notification"] = lambda: play_notes([('E5', 0.2), ('G5', 0.2)], tempo=150)
530
+ sound_map["Message"] = lambda: play_notes([('F5', 0.15), ('E5', 0.15)], tempo=170)
531
+ sound_map["IncomingCall"] = lambda: play_notes([('A5', 0.25), ('C6', 0.25)], tempo=120)
532
+ sound_map["OutgoingCall"] = lambda: play_notes([('G5', 0.25), ('B5', 0.25)], tempo=120)
533
+ sound_map["Email"] = lambda: play_notes([('F5', 0.2), ('A5', 0.2)], tempo=140)
534
+ sound_map["Success"] = lambda: play_notes([('C5', 0.2), ('E5', 0.2), ('G5', 0.2)], tempo=150)
535
+ sound_map["Fail"] = lambda: play_notes([('D4', 0.25), ('C4', 0.25)], tempo=90)
536
+ sound_map["Error"] = lambda: play_notes([('F4', 0.25), ('D4', 0.25)], tempo=90)
537
+ sound_map["Warning"] = lambda: play_notes([('B4', 0.2), ('G4', 0.2)], tempo=110)
538
+ sound_map["Connect"] = lambda: play_notes([('E5', 0.2), ('G5', 0.2)], tempo=150)
539
+ sound_map["Disconnect"] = lambda: play_notes([('B4', 0.2), ('A4', 0.2)], tempo=110)
540
+ sound_map["Loading"] = lambda: play_notes([('C5', 0.12), ('D5', 0.12), ('E5', 0.12)], tempo=220)
541
+ sound_map["Done"] = lambda: play_notes([('G5', 0.2), ('C6', 0.2)], tempo=140)
542
+ sound_map["Save"] = lambda: play_notes([('E5', 0.18), ('C5', 0.18)], tempo=140)
543
+ sound_map["Delete"] = lambda: play_notes([('C4', 0.2), ('E4', 0.2)], tempo=110)
544
+ sound_map["Copy"] = lambda: play_notes([('G4', 0.15), ('B4', 0.15)], tempo=180)
545
+ sound_map["Paste"] = lambda: play_notes([('A4', 0.15), ('C5', 0.15)], tempo=180)
546
+ sound_map["Cut"] = lambda: play_notes([('F4', 0.15), ('D4', 0.15)], tempo=170)
547
+ sound_map["Refresh"] = lambda: play_notes([('C5', 0.12), ('D5', 0.12), ('C5', 0.12)], tempo=220)
548
+ sound_map["Sync"] = lambda: play_notes([('E4', 0.12), ('G4', 0.12), ('B4', 0.12)], tempo=200)
549
+ sound_map["Update"] = lambda: play_notes([('A4', 0.2), ('B4', 0.2)], tempo=140)
550
+ sound_map["Install"] = lambda: play_notes([('C4', 0.12), ('E4', 0.12), ('G4', 0.2)], tempo=170)
551
+ sound_map["Boot"] = lambda: play_notes([('C5', 0.25), ('E5', 0.25)], tempo=120)
552
+ sound_map["Shutdown"] = lambda: play_notes([('A3', 0.4), ('F3', 0.4)], tempo=65, waveform='triangle')
553
+ sound_map["Tap"] = lambda: play_notes([('D5', 0.08)], tempo=220)
554
+ sound_map["Pop"] = lambda: play_notes([('F5', 0.1)], tempo=200)
555
+ sound_map["Beep"] = lambda: play_notes([('G4', 0.1)], tempo=220)
556
+ sound_map["Ding"] = lambda: play_notes([('E5', 0.18)], tempo=190)
557
+ sound_map["ClickSoft"] = lambda: play_notes([('F4', 0.1)], tempo=210)
558
+ sound_map["ClickHard"] = lambda: play_notes([('C4', 0.1)], tempo=200)
559
+ sound_map["Swipe"] = lambda: play_notes([('G5', 0.1), ('A5', 0.1)], tempo=180)
560
+ sound_map["Slide"] = lambda: play_notes([('A4', 0.1), ('G4', 0.1)], tempo=180)
561
+ sound_map["Open"] = lambda: play_notes([('E5', 0.2), ('G5', 0.2)], tempo=150)
562
+ sound_map["Close"] = lambda: play_notes([('D4', 0.2), ('C4', 0.2)], tempo=110)
563
+ sound_map["MenuOpen"] = lambda: play_notes([('G4', 0.12), ('A4', 0.12)], tempo=200)
564
+ sound_map["MenuClose"] = lambda: play_notes([('E4', 0.12), ('D4', 0.12)], tempo=190)
565
+ sound_map["TabSwitch"] = lambda: play_notes([('F4', 0.12), ('A4', 0.12)], tempo=190)
566
+ sound_map["Drag"] = lambda: play_notes([('C4', 0.12), ('D4', 0.12)], tempo=180)
567
+ sound_map["Drop"] = lambda: play_notes([('E4', 0.18), ('C4', 0.18)], tempo=140)
568
+ sound_map["Focus"] = lambda: play_notes([('A5', 0.1), ('G5', 0.1)], tempo=200)
569
+ sound_map["Unfocus"] = lambda: play_notes([('D4', 0.1), ('C4', 0.1)], tempo=180)
570
+ sound_map["SelectItem"] = lambda: play_notes([('B4', 0.15), ('D5', 0.15)], tempo=170)
571
+ sound_map["Deselect"] = lambda: play_notes([('C4', 0.15), ('Bb3', 0.15)], tempo=160)
572
+ sound_map["Confirm"] = lambda: play_notes([('C5', 0.2), ('G5', 0.2)], tempo=150)
573
+ sound_map["Back"] = lambda: play_notes([('D4', 0.2), ('C4', 0.2)], tempo=120)
574
+ sound_map["Forward"] = lambda: play_notes([('G4', 0.2), ('A4', 0.2)], tempo=140)
575
+ sound_map["Scroll"] = lambda: play_notes([('E5', 0.08), ('F5', 0.08), ('G5', 0.08)], tempo=240)
576
+ sound_map["ZoomIn"] = lambda: play_notes([('C5', 0.2), ('E5', 0.2)], tempo=160)
577
+ sound_map["ZoomOut"] = lambda: play_notes([('G4', 0.2), ('E4', 0.2)], tempo=120)
578
+ sound_map["Laser"] = lambda: play_notes([('A5', 0.1), ('C6', 0.1), ('E6', 0.15)], tempo=220, waveform='square')
579
+ sound_map["Plasma"] = lambda: play_notes([('F5', 0.15), ('G5', 0.15), ('A5', 0.2)], tempo=200, waveform='triangle')
580
+ sound_map["Warp"] = lambda: play_notes([('C4', 0.1), ('C5', 0.3)], tempo=130, waveform='triangle')
581
+ sound_map["Robot"] = lambda: play_notes([('G3', 0.2), ('D4', 0.2), ('G4', 0.2)], tempo=110, waveform='square')
582
+ sound_map["AI"] = lambda: play_notes([('F4', 0.15), ('A4', 0.15), ('C5', 0.2)], tempo=140, waveform='sine')
583
+ sound_map["Drone"] = lambda: play_notes([('D3', 0.4), ('F3', 0.4)], tempo=70, waveform='triangle')
584
+ sound_map["Scan"] = lambda: play_notes([('E4', 0.12), ('F4', 0.12), ('G4', 0.12)], tempo=210)
585
+ sound_map["Hack"] = lambda: play_notes([('D4', 0.1), ('F4', 0.1), ('A4', 0.2)], tempo=180, waveform='square')
586
+ sound_map["Glitch"] = lambda: play_notes([('G5', 0.05), ('C5', 0.05), ('E5', 0.05)], tempo=260, waveform='square')
587
+ sound_map["Signal"] = lambda: play_notes([('A4', 0.2), ('C5', 0.2)], tempo=160)
588
+
589
+ # Add 100 musical-style sound names with melodic variations.
590
+ base_sequences = [
591
+ [('C4', 0.2), ('E4', 0.2), ('G4', 0.2), ('C5', 0.3)],
592
+ [('D4', 0.18), ('F#4', 0.18), ('A4', 0.18), ('D5', 0.3)],
593
+ [('E4', 0.15), ('G#4', 0.15), ('B4', 0.15), ('E5', 0.3)],
594
+ [('F4', 0.2), ('A4', 0.2), ('C5', 0.2), ('F5', 0.3)],
595
+ [('G4', 0.15), ('B4', 0.15), ('D5', 0.15), ('G5', 0.3)],
596
+ [('A4', 0.18), ('C#5', 0.18), ('E5', 0.18), ('A5', 0.3)],
597
+ [('B4', 0.15), ('D5', 0.15), ('F#5', 0.15), ('B5', 0.3)],
598
+ ]
599
+ for i in range(1, 101):
600
+ pattern = base_sequences[(i - 1) % len(base_sequences)]
601
+ tempo = 100 + ((i - 1) % 5) * 10
602
+ waveform = 'triangle' if i % 7 == 0 else 'square' if i % 11 == 0 else 'sine'
603
+ sound_map[f"Melody{i:03d}"] = lambda p=pattern, t=tempo, w=waveform: play_notes(p, tempo=t, waveform=w)
604
+
605
+ # Add 300 extra musical-style sound names with richer, more varied melodic textures.
606
+ extra_sequences = [
607
+ [('C4', 0.15), ('D4', 0.15), ('E4', 0.15), ('G4', 0.25)],
608
+ [('E4', 0.18), ('G4', 0.18), ('B4', 0.18), ('E5', 0.3)],
609
+ [('G3', 0.2), ('B3', 0.2), ('D4', 0.2), ('G4', 0.3)],
610
+ [('A3', 0.16), ('C4', 0.16), ('E4', 0.16), ('A4', 0.28)],
611
+ [('F4', 0.14), ('A4', 0.14), ('C5', 0.14), ('F5', 0.28)],
612
+ [('D4', 0.2), ('F#4', 0.15), ('A4', 0.15), ('D5', 0.3)],
613
+ [('B3', 0.2), ('D4', 0.2), ('F#4', 0.2), ('B4', 0.3)],
614
+ [('C5', 0.12), ('E5', 0.12), ('G5', 0.12), ('C6', 0.24)],
615
+ [('A4', 0.15), ('B4', 0.15), ('C#5', 0.15), ('E5', 0.3)],
616
+ [('G4', 0.18), ('A4', 0.18), ('B4', 0.18), ('D5', 0.3)],
617
+ [('F#4', 0.14), ('A4', 0.14), ('C#5', 0.14), ('F#5', 0.3)],
618
+ [('E4', 0.16), ('F#4', 0.16), ('G#4', 0.16), ('B4', 0.3)],
619
+ [('D5', 0.15), ('C5', 0.15), ('A4', 0.15), ('F4', 0.25)],
620
+ [('C4', 0.2), ('B3', 0.2), ('A3', 0.2), ('G3', 0.3)],
621
+ [('E5', 0.1), ('D5', 0.1), ('C5', 0.1), ('B4', 0.1), ('A4', 0.2)],
622
+ ]
623
+ for i in range(101, 401):
624
+ pattern = extra_sequences[(i - 101) % len(extra_sequences)]
625
+ tempo = 90 + ((i - 101) % 10) * 7
626
+ waveform = 'square' if i % 13 == 0 else 'triangle' if i % 7 == 0 else 'sine'
627
+ volume = 0.35 + ((i - 101) % 4) * 0.05
628
+ sound_map[f"Melody{i:03d}"] = lambda p=pattern, t=tempo, w=waveform, v=volume: play_notes(p, tempo=t, volume=v, waveform=w)
629
+
630
+ # General utilities added to make the library competitive.
631
+
632
+ # =========================
633
+ # VALIDATION UTILITIES
634
+ # =========================
635
+
636
+ def is_integer(value):
637
+ return isinstance(value, int) and not isinstance(value, bool)
638
+
639
+
640
+ def is_float(value):
641
+ return isinstance(value, float)
642
+
643
+
644
+ def is_string(value):
645
+ return isinstance(value, str)
646
+
647
+
648
+ def is_list(value):
649
+ return isinstance(value, list)
650
+
651
+
652
+ def is_dict(value):
653
+ return isinstance(value, dict)
654
+
655
+
656
+ def is_boolean(value):
657
+ return isinstance(value, bool)
658
+
659
+
660
+ def validate_schema(data, schema, path=''):
661
+ errors = []
662
+ if not isinstance(schema, dict):
663
+ return False, ['schema must be a dict']
664
+
665
+ if 'type' in schema:
666
+ expected = schema['type']
667
+ if expected == 'string' and not isinstance(data, str):
668
+ errors.append(f'{path or "root"}: expected string')
669
+ elif expected == 'integer' and not is_integer(data):
670
+ errors.append(f'{path or "root"}: expected integer')
671
+ elif expected == 'number' and not isinstance(data, (int, float)):
672
+ errors.append(f'{path or "root"}: expected number')
673
+ elif expected == 'boolean' and not isinstance(data, bool):
674
+ errors.append(f'{path or "root"}: expected boolean')
675
+ elif expected == 'object' and not isinstance(data, dict):
676
+ errors.append(f'{path or "root"}: expected object')
677
+ elif expected == 'array' and not isinstance(data, list):
678
+ errors.append(f'{path or "root"}: expected array')
679
+
680
+ if isinstance(data, dict) and 'properties' in schema:
681
+ for key, subschema in schema['properties'].items():
682
+ if key in data:
683
+ valid, sub_errors = validate_schema(data[key], subschema, f'{path}.{key}' if path else key)
684
+ errors.extend(sub_errors)
685
+ elif subschema.get('required', False):
686
+ errors.append(f'{path}.{key}' if path else key + ': required field missing')
687
+
688
+ if isinstance(data, list) and 'items' in schema:
689
+ for index, item in enumerate(data):
690
+ valid, sub_errors = validate_schema(item, schema['items'], f'{path}[{index}]')
691
+ errors.extend(sub_errors)
692
+
693
+ return len(errors) == 0, errors
694
+
695
+
696
+ def ensure_schema(data, schema):
697
+ valid, errors = validate_schema(data, schema)
698
+ if not valid:
699
+ raise ValueError('Validation failed: ' + '; '.join(errors))
700
+ return True
701
+
702
+
703
+ def has_keys(obj, keys):
704
+ return all(k in obj for k in keys)
705
+
706
+
707
+ def assert_type(value, expected_type):
708
+ return isinstance(value, expected_type)
709
+
710
+
711
+ def is_truthy(value):
712
+ return bool(value)
713
+
714
+
715
+ def is_blank(text):
716
+ return not str(text).strip()
717
+
718
+
719
+ # =========================
720
+ # JSON & CONFIG UTILITIES
721
+ # =========================
722
+
723
+ def load_json(path, encoding='utf-8'):
724
+ with open(path, 'r', encoding=encoding) as file:
725
+ return json.load(file)
726
+
727
+
728
+ def save_json(path, data, encoding='utf-8', indent=4):
729
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
730
+ with open(path, 'w', encoding=encoding) as file:
731
+ json.dump(data, file, ensure_ascii=False, indent=indent)
732
+
733
+
734
+ def merge_dicts(*dicts, deep=True):
735
+ result = {}
736
+ for d in dicts:
737
+ if not isinstance(d, dict):
738
+ continue
739
+ for key, value in d.items():
740
+ if deep and key in result and isinstance(result[key], dict) and isinstance(value, dict):
741
+ result[key] = merge_dicts(result[key], value, deep=True)
742
+ else:
743
+ result[key] = value
744
+ return result
745
+
746
+
747
+ def deep_get(data, path, default=None, separator='.'):
748
+ if data is None:
749
+ return default
750
+ if separator in path:
751
+ parts = path.split(separator)
752
+ else:
753
+ parts = [path]
754
+ current = data
755
+ for part in parts:
756
+ if isinstance(current, dict) and part in current:
757
+ current = current[part]
758
+ else:
759
+ return default
760
+ return current
761
+
762
+
763
+ def deep_set(data, path, value, separator='.'):
764
+ if not isinstance(data, dict):
765
+ raise ValueError('deep_set target must be a dict')
766
+ keys = path.split(separator)
767
+ current = data
768
+ for key in keys[:-1]:
769
+ if key not in current or not isinstance(current[key], dict):
770
+ current[key] = {}
771
+ current = current[key]
772
+ current[keys[-1]] = value
773
+ return data
774
+
775
+
776
+ def load_json_url(url, headers=None, timeout=10):
777
+ request = urllib.request.Request(url, headers=headers or {})
778
+ with urllib.request.urlopen(request, timeout=timeout) as response:
779
+ return json.loads(response.read().decode('utf-8'))
780
+
781
+
782
+ # =========================
783
+ # HTTP UTILITIES
784
+ # =========================
785
+
786
+ def http_get(url, headers=None, timeout=10):
787
+ request = urllib.request.Request(url, headers=headers or {}, method='GET')
788
+ with urllib.request.urlopen(request, timeout=timeout) as response:
789
+ return response.read(), response.getcode(), dict(response.headers)
790
+
791
+
792
+ def http_post(url, data=None, json_data=None, headers=None, timeout=10):
793
+ request_headers = headers.copy() if headers else {}
794
+ body = None
795
+ if json_data is not None:
796
+ body = json.dumps(json_data).encode('utf-8')
797
+ request_headers['Content-Type'] = 'application/json'
798
+ elif data is not None:
799
+ if isinstance(data, dict):
800
+ body = urllib.parse.urlencode(data).encode('utf-8')
801
+ else:
802
+ body = str(data).encode('utf-8')
803
+ request = urllib.request.Request(url, data=body, headers=request_headers, method='POST')
804
+ with urllib.request.urlopen(request, timeout=timeout) as response:
805
+ return response.read(), response.getcode(), dict(response.headers)
806
+
807
+
808
+ def fetch_json(url, headers=None, timeout=10):
809
+ body, code, headers = http_get(url, headers=headers, timeout=timeout)
810
+ return json.loads(body.decode('utf-8'))
811
+
812
+
813
+ def check_url(url, timeout=5):
814
+ try:
815
+ _, code, _ = http_get(url, timeout=timeout)
816
+ return 200 <= code < 400
817
+ except Exception:
818
+ return False
819
+
820
+
821
+ def download_file(url, dest, chunk_size=8192, timeout=10):
822
+ Path(dest).parent.mkdir(parents=True, exist_ok=True)
823
+ request = urllib.request.Request(url)
824
+ with urllib.request.urlopen(request, timeout=timeout) as response:
825
+ with open(dest, 'wb') as out_file:
826
+ while True:
827
+ chunk = response.read(chunk_size)
828
+ if not chunk:
829
+ break
830
+ out_file.write(chunk)
831
+ return dest
832
+
833
+
834
+ # =========================
835
+ # DATE / TIME UTILITIES
836
+ # =========================
837
+
838
+ def current_timestamp():
839
+ return datetime.datetime.now().timestamp()
840
+
841
+
842
+ def format_datetime(value=None, fmt='%Y-%m-%d %H:%M:%S'):
843
+ if value is None:
844
+ value = datetime.datetime.now()
845
+ if isinstance(value, (int, float)):
846
+ value = datetime.datetime.fromtimestamp(value)
847
+ return value.strftime(fmt)
848
+
849
+
850
+ def parse_datetime(text, fmt='%Y-%m-%d %H:%M:%S'):
851
+ return datetime.datetime.strptime(str(text), fmt)
852
+
853
+
854
+ def relative_time(value, now=None):
855
+ if now is None:
856
+ now = datetime.datetime.now()
857
+ if isinstance(value, (int, float)):
858
+ value = datetime.datetime.fromtimestamp(value)
859
+ delta = now - value
860
+ seconds = int(delta.total_seconds())
861
+ if seconds < 0:
862
+ return 'in the future'
863
+ if seconds < 60:
864
+ return f'{seconds}s ago'
865
+ if seconds < 3600:
866
+ return f'{seconds // 60}m ago'
867
+ if seconds < 86400:
868
+ return f'{seconds // 3600}h ago'
869
+ return f'{seconds // 86400}d ago'
870
+
871
+
872
+ def countdown(seconds, callback=None, interval=1):
873
+ start = time.time()
874
+ remaining = seconds
875
+ while remaining > 0:
876
+ time.sleep(min(interval, remaining))
877
+ remaining = seconds - (time.time() - start)
878
+ if callback:
879
+ callback(max(0, int(remaining)))
880
+ return 0
881
+
882
+
883
+ def is_weekend(value=None):
884
+ if value is None:
885
+ value = datetime.datetime.now()
886
+ if isinstance(value, (int, float)):
887
+ value = datetime.datetime.fromtimestamp(value)
888
+ return value.weekday() >= 5
889
+
890
+
891
+ def add_business_days(value, days):
892
+ if isinstance(value, (int, float)):
893
+ value = datetime.datetime.fromtimestamp(value)
894
+ result = value
895
+ while days > 0:
896
+ result += datetime.timedelta(days=1)
897
+ if result.weekday() < 5:
898
+ days -= 1
899
+ return result
900
+
901
+
902
+ # =========================
903
+ # MATH / STATISTICS UTILITIES
904
+ # =========================
905
+
906
+ def clamp(value, minimum, maximum):
907
+ return max(minimum, min(maximum, value))
908
+
909
+
910
+ def lerp(start, end, fraction):
911
+ return start + (end - start) * fraction
912
+
913
+
914
+ def remap(value, old_min, old_max, new_min, new_max):
915
+ if old_max == old_min:
916
+ raise ValueError('old_min and old_max cannot be equal')
917
+ ratio = (value - old_min) / (old_max - old_min)
918
+ return new_min + ratio * (new_max - new_min)
919
+
920
+
921
+ def mean_values(values):
922
+ return statistics.mean(values)
923
+
924
+
925
+ def median_values(values):
926
+ return statistics.median(values)
927
+
928
+
929
+ def mode_values(values):
930
+ try:
931
+ return statistics.mode(values)
932
+ except statistics.StatisticsError:
933
+ return None
934
+
935
+
936
+ def variance(values, sample=False):
937
+ return statistics.pvariance(values) if not sample else statistics.variance(values)
938
+
939
+
940
+ def stddev(values, sample=False):
941
+ return math.sqrt(variance(values, sample=sample))
942
+
943
+
944
+ def percent_change(original, new):
945
+ if original == 0:
946
+ return float('inf') if new else 0.0
947
+ return ((new - original) / abs(original)) * 100.0
948
+
949
+
950
+ def safe_divide(a, b, default=0.0):
951
+ try:
952
+ return a / b
953
+ except Exception:
954
+ return default
955
+
956
+
957
+ def random_choice_weighted(choices):
958
+ total = sum(weight for item, weight in choices)
959
+ if total <= 0:
960
+ return None
961
+ r = random.uniform(0, total)
962
+ upto = 0
963
+ for item, weight in choices:
964
+ if upto + weight >= r:
965
+ return item
966
+ upto += weight
967
+ return None
968
+
969
+
970
+ # =========================
971
+ # PROCESS & COMMAND UTILITIES
972
+ # =========================
973
+
974
+ def run_command(command, shell=False, cwd=None, env=None, timeout=None):
975
+ if isinstance(command, str) and not shell:
976
+ command = command.split()
977
+ result = subprocess.run(command, shell=shell, cwd=cwd, env=env, capture_output=True, text=True, timeout=timeout)
978
+ return {
979
+ 'returncode': result.returncode,
980
+ 'stdout': result.stdout,
981
+ 'stderr': result.stderr,
982
+ }
983
+
984
+
985
+ def run_command_async(command, callback=None, shell=False, cwd=None, env=None, timeout=None):
986
+ def worker():
987
+ result = run_command(command, shell=shell, cwd=cwd, env=env, timeout=timeout)
988
+ if callback:
989
+ callback(result)
990
+ thread = threading.Thread(target=worker, daemon=True)
991
+ thread.start()
992
+ return thread
993
+
994
+
995
+ def which(program):
996
+ return shutil.which(program)
997
+
998
+
999
+ def is_process_running(name):
1000
+ if platform.system() == 'Windows':
1001
+ tasklist = subprocess.run(['tasklist'], capture_output=True, text=True)
1002
+ return name.lower() in tasklist.stdout.lower()
1003
+ else:
1004
+ ps = subprocess.run(['ps', 'ax'], capture_output=True, text=True)
1005
+ return name.lower() in ps.stdout.lower()
1006
+
1007
+
1008
+ def kill_process(pid):
1009
+ try:
1010
+ os.kill(pid, 9)
1011
+ return True
1012
+ except Exception:
1013
+ return False
1014
+
1015
+
1016
+ def copy_file(src, dst):
1017
+ Path(dst).parent.mkdir(parents=True, exist_ok=True)
1018
+ shutil.copy2(src, dst)
1019
+ return dst
1020
+
1021
+
1022
+ def move_file(src, dst):
1023
+ Path(dst).parent.mkdir(parents=True, exist_ok=True)
1024
+ shutil.move(src, dst)
1025
+ return dst
1026
+
1027
+
1028
+ def delete_file(path):
1029
+ try:
1030
+ Path(path).unlink()
1031
+ return True
1032
+ except Exception:
1033
+ return False
1034
+
1035
+
1036
+ # =========================
1037
+ # DATA STRUCTURE UTILITIES
1038
+ # =========================
1039
+
1040
+ def chunk_list(sequence, chunk_size):
1041
+ if chunk_size <= 0:
1042
+ raise ValueError('chunk_size must be positive')
1043
+ return [sequence[i:i + chunk_size] for i in range(0, len(sequence), chunk_size)]
1044
+
1045
+
1046
+ def flatten_list(nested_list):
1047
+ result = []
1048
+ for item in nested_list:
1049
+ if isinstance(item, list):
1050
+ result.extend(flatten_list(item))
1051
+ else:
1052
+ result.append(item)
1053
+ return result
1054
+
1055
+
1056
+ def unique_preserve(sequence):
1057
+ seen = set()
1058
+ result = []
1059
+ for item in sequence:
1060
+ if item not in seen:
1061
+ seen.add(item)
1062
+ result.append(item)
1063
+ return result
1064
+
1065
+
1066
+ def group_by(sequence, key_function):
1067
+ groups = defaultdict(list)
1068
+ for item in sequence:
1069
+ groups[key_function(item)].append(item)
1070
+ return dict(groups)
1071
+
1072
+
1073
+ def coalesce(*values):
1074
+ for value in values:
1075
+ if value is not None:
1076
+ return value
1077
+ return None
1078
+
1079
+
1080
+ def dict_merge(*dicts):
1081
+ merged = {}
1082
+ for d in dicts:
1083
+ if isinstance(d, dict):
1084
+ merged.update(d)
1085
+ return merged
1086
+
1087
+
1088
+ # =========================
1089
+ # SETTINGS & STORAGE UTILITIES
1090
+ # =========================
1091
+
1092
+ def load_settings(path, default=None, encoding='utf-8'):
1093
+ if not Path(path).exists():
1094
+ return default if default is not None else {}
1095
+ return load_json(path, encoding=encoding)
1096
+
1097
+
1098
+ def save_settings(path, settings, encoding='utf-8', indent=4):
1099
+ return save_json(path, settings, encoding=encoding, indent=indent)
1100
+
1101
+
1102
+ def get_setting(settings, key, default=None):
1103
+ if isinstance(settings, dict):
1104
+ return deep_get(settings, key, default=default)
1105
+ return default
1106
+
1107
+
1108
+ def set_setting(settings, key, value):
1109
+ if not isinstance(settings, dict):
1110
+ raise ValueError('settings must be a dict')
1111
+ return deep_set(settings, key, value)
1112
+
1113
+
1114
+ def update_settings(settings, updates):
1115
+ if not isinstance(settings, dict) or not isinstance(updates, dict):
1116
+ raise ValueError('update_settings requires dicts')
1117
+ return merge_dicts(settings, updates)
1118
+
1119
+
1120
+ def flatten_settings(settings, separator='.'):
1121
+ result = {}
1122
+ def _flatten(source, parent_key=''):
1123
+ for key, value in source.items():
1124
+ full_key = f'{parent_key}{separator}{key}' if parent_key else key
1125
+ if isinstance(value, dict):
1126
+ _flatten(value, full_key)
1127
+ else:
1128
+ result[full_key] = value
1129
+ _flatten(settings)
1130
+ return result
1131
+
1132
+
1133
+ # =========================
1134
+ # EVENT BUS / PUBLISH-SUBSCRIBE
1135
+ # =========================
1136
+
1137
+ class EventBus:
1138
+ def __init__(self):
1139
+ self._listeners = defaultdict(list)
1140
+
1141
+ def subscribe(self, event_name, listener):
1142
+ if callable(listener):
1143
+ self._listeners[event_name].append(listener)
1144
+ return True
1145
+ return False
1146
+
1147
+ def unsubscribe(self, event_name, listener=None):
1148
+ if event_name not in self._listeners:
1149
+ return False
1150
+ if listener is None:
1151
+ self._listeners.pop(event_name, None)
1152
+ return True
1153
+ try:
1154
+ self._listeners[event_name].remove(listener)
1155
+ return True
1156
+ except ValueError:
1157
+ return False
1158
+
1159
+ def emit(self, event_name, *args, **kwargs):
1160
+ for listener in list(self._listeners.get(event_name, [])):
1161
+ try:
1162
+ listener(*args, **kwargs)
1163
+ except Exception:
1164
+ pass
1165
+
1166
+ def clear(self):
1167
+ self._listeners.clear()
1168
+
1169
+
1170
+ # =========================
1171
+ # LOGGING UTILITIES
1172
+ # =========================
1173
+
1174
+ _log_file = None
1175
+
1176
+
1177
+ def set_log_file(path, level=logging.INFO, mode='a'):
1178
+ global _log_file
1179
+ logger = logging.getLogger(__name__)
1180
+ logger.setLevel(level)
1181
+ formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s')
1182
+ file_handler = logging.FileHandler(path, mode=mode, encoding='utf-8')
1183
+ file_handler.setFormatter(formatter)
1184
+ logger.handlers = [file_handler]
1185
+ _log_file = path
1186
+ return path
1187
+
1188
+
1189
+ def log_info(message):
1190
+ logging.getLogger(__name__).info(str(message))
1191
+
1192
+
1193
+ def log_warning(message):
1194
+ logging.getLogger(__name__).warning(str(message))
1195
+
1196
+
1197
+ def log_error(message):
1198
+ logging.getLogger(__name__).error(str(message))
1199
+
1200
+
1201
+ def log_debug(message):
1202
+ logging.getLogger(__name__).debug(str(message))
1203
+
1204
+
1205
+ def get_log_file():
1206
+ return _log_file
1207
+
1208
+
1209
+ def configure_logging(level=logging.INFO, fmt='[%(levelname)s] %(message)s'):
1210
+ logging.basicConfig(level=level, format=fmt)
1211
+ return logging.getLogger(__name__)
1212
+
1213
+
1214
+ # =========================
1215
+ # CLI / OUTPUT UTILITIES
1216
+ # =========================
1217
+
1218
+ def print_table(rows, headers=None, padding=2):
1219
+ if headers:
1220
+ rows = [headers] + rows
1221
+ widths = [max(len(str(value)) for value in column) for column in zip(*rows)]
1222
+ lines = []
1223
+ for row in rows:
1224
+ line = ' '.join(str(value).ljust(widths[index] + padding) for index, value in enumerate(row))
1225
+ lines.append(line)
1226
+ print('\n'.join(lines))
1227
+
1228
+
1229
+ def progress_bar(iterable, prefix='', size=40, fill='█'):
1230
+ total = len(iterable)
1231
+ for index, item in enumerate(iterable, start=1):
1232
+ filled = int(size * index / total)
1233
+ bar = fill * filled + '-' * (size - filled)
1234
+ print(f'\r{prefix} |{bar}| {index}/{total}', end='')
1235
+ yield item
1236
+ print()
1237
+
1238
+
1239
+ def prompt_choices(root, title, options, default_index=0):
1240
+ if not options:
1241
+ return None
1242
+ choice = simpledialog.askstring(title, 'Choose: ' + ', '.join(str(i+1) + ':' + str(opt) for i, opt in enumerate(options)), initialvalue=str(default_index+1), parent=root)
1243
+ try:
1244
+ idx = int(choice) - 1
1245
+ return options[idx] if 0 <= idx < len(options) else None
1246
+ except Exception:
1247
+ return None
1248
+
1249
+
1250
+ def clear_console():
1251
+ if platform.system() == 'Windows':
1252
+ os.system('cls')
1253
+ else:
1254
+ os.system('clear')
1255
+
1256
+
1257
+ def confirm_action(message='Proceed?', default=True):
1258
+ return messagebox.askyesno('Confirm', message, icon='question')
1259
+
1260
+
1261
+ def show_progress(title, current, total):
1262
+ percent = int((current / total) * 100) if total else 0
1263
+ return f'{title}: {percent}% ({current}/{total})'
1264
+
1265
+
1266
+ def play(name):
1267
+ if name in sound_map:
1268
+ sound_map[name]()
1269
+ else:
1270
+ print(f"Sound '{name}' not found in sound_map.")
1271
+
1272
+
1273
+ def add_sound(name, func):
1274
+ sound_map[name] = lambda: func()
1275
+
1276
+ # =========================
1277
+ # MAJOR ARCHITECTURE EXPANSION
1278
+ # =========================
1279
+
1280
+ class PluginManager:
1281
+ """Manages plugins and extension lifecycle."""
1282
+
1283
+ def __init__(self):
1284
+ self.plugins = {}
1285
+
1286
+ def register(self, name, plugin):
1287
+ self.plugins[name] = plugin
1288
+ return True
1289
+
1290
+ def unregister(self, name):
1291
+ if name in self.plugins:
1292
+ del self.plugins[name]
1293
+ return True
1294
+ return False
1295
+
1296
+ def get_plugin(self, name, default=None):
1297
+ return self.plugins.get(name, default)
1298
+
1299
+ def list_plugins(self):
1300
+ return list(self.plugins.keys())
1301
+
1302
+
1303
+ class WorkflowEngine:
1304
+ """Orchestrates workflow stages with dependencies."""
1305
+
1306
+ def __init__(self):
1307
+ self.stages = {}
1308
+ self.dependencies = {}
1309
+
1310
+ def add_stage(self, name, callback, depends_on=None):
1311
+ self.stages[name] = callback
1312
+ self.dependencies[name] = depends_on or []
1313
+ return self
1314
+
1315
+ def run_stage(self, name, context=None):
1316
+ if name not in self.stages:
1317
+ raise KeyError(f'Stage {name} is not registered')
1318
+ context = context or {}
1319
+ for dependency in self.dependencies.get(name, []):
1320
+ self.run_stage(dependency, context)
1321
+ return self.stages[name](context)
1322
+
1323
+ def run_all(self, context=None):
1324
+ context = context or {}
1325
+ for name in list(self.stages.keys()):
1326
+ self.run_stage(name, context)
1327
+ return context
1328
+
1329
+
1330
+ class ResourceRegistry:
1331
+ """Tracks named resources across application layers."""
1332
+
1333
+ def __init__(self):
1334
+ self.resources = {}
1335
+
1336
+ def register_resource(self, name, resource):
1337
+ self.resources[name] = resource
1338
+ return self
1339
+
1340
+ def get_resource(self, name, default=None):
1341
+ return self.resources.get(name, default)
1342
+
1343
+ def list_resources(self):
1344
+ return list(self.resources.keys())
1345
+
1346
+
1347
+ class DistributedJobScheduler:
1348
+ """Schedules jobs using a simple distributed work queue."""
1349
+
1350
+ def __init__(self):
1351
+ self.jobs = []
1352
+ self.results = {}
1353
+
1354
+ def schedule(self, job_id, callback, *args, **kwargs):
1355
+ self.jobs.append((job_id, callback, args, kwargs))
1356
+ return self
1357
+
1358
+ def run_next(self):
1359
+ if not self.jobs:
1360
+ return None
1361
+ job_id, callback, args, kwargs = self.jobs.pop(0)
1362
+ result = callback(*args, **kwargs)
1363
+ self.results[job_id] = result
1364
+ return result
1365
+
1366
+ def run_all(self):
1367
+ while self.jobs:
1368
+ self.run_next()
1369
+ return self.results
1370
+
1371
+
1372
+ class SecurityPolicyManager:
1373
+ """Manages security policies and validation rules."""
1374
+
1375
+ def __init__(self):
1376
+ self.policies = {}
1377
+
1378
+ def add_policy(self, name, callback):
1379
+ self.policies[name] = callback
1380
+ return self
1381
+
1382
+ def validate(self, name, context=None):
1383
+ if name not in self.policies:
1384
+ raise KeyError(f'Policy {name} not found')
1385
+ return bool(self.policies[name](context or {}))
1386
+
1387
+ def list_policies(self):
1388
+ return list(self.policies.keys())
1389
+
1390
+
1391
+ class AuditTrail:
1392
+ """Records immutable audit events."""
1393
+
1394
+ def __init__(self):
1395
+ self.events = []
1396
+
1397
+ def record_event(self, event_type, details=None):
1398
+ self.events.append({
1399
+ 'type': event_type,
1400
+ 'details': details,
1401
+ 'timestamp': current_timestamp(),
1402
+ })
1403
+ return self
1404
+
1405
+ def query(self, event_type=None):
1406
+ if event_type is None:
1407
+ return list(self.events)
1408
+ return [item for item in self.events if item['type'] == event_type]
1409
+
1410
+
1411
+ class ModelRegistry:
1412
+ """Stores models and their metadata."""
1413
+
1414
+ def __init__(self):
1415
+ self.models = {}
1416
+
1417
+ def register_model(self, name, model, metadata=None):
1418
+ self.models[name] = {
1419
+ 'model': model,
1420
+ 'metadata': metadata or {},
1421
+ }
1422
+ return self
1423
+
1424
+ def get_model(self, name):
1425
+ return self.models.get(name)
1426
+
1427
+ def list_models(self):
1428
+ return list(self.models.keys())
1429
+
1430
+
1431
+ class AnalyticsEngine:
1432
+ """Processes metrics and analytics streams."""
1433
+
1434
+ def __init__(self):
1435
+ self.events = []
1436
+
1437
+ def track_event(self, name, properties=None):
1438
+ self.events.append({'name': name, 'properties': properties or {}, 'time': current_timestamp()})
1439
+ return self
1440
+
1441
+ def summarize(self):
1442
+ return {
1443
+ 'total_events': len(self.events),
1444
+ 'event_names': list({event['name'] for event in self.events}),
1445
+ }
1446
+
1447
+
1448
+ plugin_manager = PluginManager()
1449
+ workflow_engine = WorkflowEngine()
1450
+ resource_registry = ResourceRegistry()
1451
+ job_scheduler = DistributedJobScheduler()
1452
+ security_policy_manager = SecurityPolicyManager()
1453
+ audit_trail = AuditTrail()
1454
+ model_registry = ModelRegistry()
1455
+ analytics_engine = AnalyticsEngine()