diff --git a/main.py b/main.py index e08a9319..31fb5b46 100755 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ import subprocess import sys import textwrap import threading +import signal from cli.sounds import Sound @@ -25,7 +26,6 @@ def list_cli_commands(cli_dir): ) def extract_description_via_help(cli_script_path): - """Run `script --help` and extract the first non-usage line after usage block.""" try: result = subprocess.run( [sys.executable, cli_script_path, "--help"], @@ -50,119 +50,88 @@ def play_start_intro(): Sound.play_start_sound() Sound.play_cymais_intro_sound() +def failure_with_warning_loop(): + Sound.play_finished_failed_sound() + print("Warning: command failed. Press Ctrl+C to stop sound warnings.") + try: + while True: + Sound.play_warning_sound() + except KeyboardInterrupt: + print("Warnings stopped by user.") + if __name__ == "__main__": - # Detect --no-sound option early - no_sound = '--no-sound' in sys.argv - if no_sound: + # Parse --no-sound early and remove from args + no_sound = False + if '--no-sound' in sys.argv: + no_sound = True sys.argv.remove('--no-sound') - # Warning loop for failures - def warning_loop(): - try: - while True: - Sound.play_warning_sound() - except Exception: - pass - - # Start intro sounds in background if enabled - if not no_sound: - threading.Thread(target=play_start_intro, daemon=True).start() - - script_dir = os.path.dirname(os.path.realpath(__file__)) - import signal - - # Handler for segmentation faults + # Setup segfault handler to catch crashes def segv_handler(signum, frame): - # Play failure and warning loop on segmentation fault if not no_sound: Sound.play_finished_failed_sound() - stop_event = threading.Event() - threading.Thread(target=warning_loop, args=(stop_event,), daemon=True).start() - print("Segmentation fault detected. Press Enter to stop warnings.") - input() - if not no_sound: - stop_event.set() + # Loop warning until interrupted + try: + while True: + Sound.play_warning_sound() + except KeyboardInterrupt: + pass + print("Segmentation fault detected. Exiting.") sys.exit(1) - signal.signal(signal.SIGSEGV, segv_handler) - # Detect --no-sound option - # Detect --no-sound option - no_sound = '--no-sound' in sys.argv - if no_sound: - sys.argv.remove('--no-sound') - - # Start intro sounds in background if enabled + # Play intro sounds if not no_sound: threading.Thread(target=play_start_intro, daemon=True).start() + # Change to script directory script_dir = os.path.dirname(os.path.realpath(__file__)) cli_dir = os.path.join(script_dir, "cli") os.chdir(script_dir) available_cli_commands = list_cli_commands(cli_dir) - # Special case: user ran `cymais --help` - if len(sys.argv) >= 3 and sys.argv[1] in available_cli_commands and sys.argv[2] == "--help": - cli_script_path = os.path.join(cli_dir, f"{sys.argv[1]}.py") - subprocess.run([sys.executable, cli_script_path, "--help"]) - sys.exit(0) - - # Global help - if "--help" in sys.argv or "-h" in sys.argv or len(sys.argv) == 1: - print("CyMaIS CLI – proxy to tools in ./cli/\n") - print("Usage:") - print(" cymais [options]\n") + # Handle help invocation + if len(sys.argv) == 1 or sys.argv[1] in ('-h', '--help'): + print("CyMaIS CLI – proxy to tools in ./cli/") + print("Usage: cymais [--no-sound] [options]") + print("Options:") + print(" --no-sound Suppress all sounds during execution") + print(" -h, --help Show this help message and exit") print("Available commands:") for cmd in available_cli_commands: path = os.path.join(cli_dir, f"{cmd}.py") desc = extract_description_via_help(path) print(format_command_help(cmd, desc)) - print("\nUse 'cymais --help' for details.") sys.exit(0) - # Execute command and handle exit codes or signals + # Special-case per-command help + if len(sys.argv) >= 3 and sys.argv[1] in available_cli_commands and sys.argv[2] in ('-h', '--help'): + subprocess.run([sys.executable, os.path.join(cli_dir, f"{sys.argv[1]}.py"), "--help"]) + sys.exit(0) + + # Execute chosen command + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('cli_command', choices=available_cli_commands) + parser.add_argument('cli_args', nargs=argparse.REMAINDER) + args = parser.parse_args() + + cmd_path = os.path.join(cli_dir, f"{args.cli_command}.py") + full_cmd = [sys.executable, cmd_path] + args.cli_args + try: - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("cli_command", choices=available_cli_commands) - parser.add_argument("cli_args", nargs=argparse.REMAINDER) - args = parser.parse_args() - - cli_script_path = os.path.join(cli_dir, f"{args.cli_command}.py") - full_cmd = [sys.executable, cli_script_path] + args.cli_args - proc = subprocess.Popen(full_cmd) proc.wait() - retcode = proc.returncode - - if retcode != 0: - # Failure sound - if not no_sound: - Sound.play_finished_failed_sound() - sig_msg = f" (terminated by signal {-retcode})" if retcode < 0 else '' - print(f"Command failed with exit code {retcode}{sig_msg}. Press Ctrl+C to stop warnings.") - if not no_sound: - try: - while True: - Sound.play_warning_sound() - except KeyboardInterrupt: - pass - sys.exit(retcode) - + rc = proc.returncode + if rc != 0: + print(f"Command '{args.cli_command}' failed with exit code {rc}.") + failure_with_warning_loop() + sys.exit(rc) else: if not no_sound: Sound.play_finished_successfully_sound() sys.exit(0) - except Exception as e: - # Exception handling - if not no_sound: - Sound.play_finished_failed_sound() - print(f"An exception occurred: {e}. Press Ctrl+C to stop warnings.") - if not no_sound: - try: - while True: - Sound.play_warning_sound() - except KeyboardInterrupt: - pass + print(f"Exception running command: {e}") + failure_with_warning_loop() sys.exit(1)