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
|
#
# InspIRCd -- Internet Relay Chat Daemon
#
# Copyright (C) 2026 Sadie Powell <sadie@witchery.services>
#
# 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 <http://www.gnu.org/licenses/>.
#
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]} <command> [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)
|