# # InspIRCd -- Internet Relay Chat Daemon # # Copyright (C) 2026 Sadie Powell # # This file is part of InspIRCd. InspIRCd is free software: you can # redistribute it and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation, version 2. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import os import re import sys DEBUG = int(os.getenv("INSPIRCD_DEBUG", "0")) > 0 RESET = "\x1B[0m" if sys.stdout.isatty() else "" BOLD = "\x1B[1m" if sys.stdout.isatty() else "" BOLD_RED = "\x1B[1;31m" if sys.stdout.isatty() else "" BOLD_GREEN = "\x1B[1;32m" if sys.stdout.isatty() else "" BOLD_YELLOW = "\x1B[1;33m" if sys.stdout.isatty() else "" FAINT = "\x1B[2m" if sys.stdout.isatty() else "" # Measures the length of a str with the ANSI escape codes removed. def ansi_len(text): return len(re.sub(r"\x1b\[[0-9;]*m", "", text)) # Wraps some text with console colored text. def color(text, color): return "".join([color, text, RESET]) # Prints a debug message to the standard output stream if INSPIRCD_DEBUG is set. def debug(message): if DEBUG: print(f"{BOLD}!!!{RESET} {message}", file=sys.stderr) # Prints an error message to the standard error stream and exits. def error(*messages): print(f"{BOLD_RED}Error:{RESET} ", end="", file=sys.stderr) for message in messages: print(message, file=sys.stderr) sys.exit(1) # Determines if the script has been invoked in interactive mode. def interactive(): return os.isatty(sys.stdin.fileno()) and os.isatty(sys.stdout.fileno()) # Prompts for the user to enter a boolean value. def prompt_boolean(default): while True: answer = prompt_string("yes" if default else "no").lower() if answer in ("y", "yes"): return True if answer in ("n", "no"): return False warning(f'"{answer}" is not "yes" or "no". Please try again.') # Prompts for the user to enter a string value. def prompt_string(default): answer = input(f"[{color(default, BOLD)}] => ").strip() print() return answer if answer else default # Dispatches commands to their handler method. def subcommand(args, start, commands, default="help"): commands["help"] = { "function": subcommand_help, "help": "Show this message and exit", } command = args[start].lower() if len(args) > start else default if command in commands: # The user specified an exact match. command_match = command else: # Also allow users to specify a partial command for convenience. command_matches = [c for c in commands if c.startswith(command)] command_match = command_matches[0] if len(command_matches) == 1 else None if not command_match: prefix = " ".join(sys.argv[0:start]) error( f"{command} is not a recognised subcommand.", f"Use `{prefix} help` for a list of subcommands.", ) debug(f"Command match: {command} => {command_match}") command_data = commands[command_match] return command_data["function"](commands, args, start + 1) # Prints the help output for a subcommand. def subcommand_help(commands, args, start): print(f"{BOLD}Usage{RESET}: {args[0]} [options...]") print() print("Commands:") command_end = start - (1 if len(args) >= start else 0) command_prefix = "".join([f"{a} " for a in args[1:command_end]]) help_entries = {} for command_name, command in commands.items(): if "help" not in command: continue help_entries[f"{command_prefix}{command_name}"] = command["help"] option_padding = " " * len(command_prefix) for option_name, option_help in command.get("options", {}).items(): help_entries[f"{option_padding}{command_name} {option_name}"] = option_help help_padding = max(len(e) for e in help_entries) for help_name, help_message in help_entries.items(): print(f" {help_name:<{help_padding}} {help_message}") # Formats and prints a table to the console. def table(headers, rows, separator="|"): if headers is not None: rows.insert(0, ["-" * ansi_len(header) for header in headers]) rows.insert(0, [color(header, BOLD) for header in headers]) widths = [max(ansi_len(field) for field in col) for col in zip(*rows)] for row in rows: padded_row = [] for i, item in enumerate(row): padding = " " * (widths[i] - ansi_len(item)) padded_row.append(f"{item}{padding}") print(f" {separator} ".join(padded_row).strip()) # Prints a warning message to the standard error stream. def warning(*messages): print(f"{BOLD_YELLOW}Warning:{RESET} ", end="", file=sys.stderr) for message in messages: print(message, file=sys.stderr)