/
dieoska
/
ddnet
Обзор
Документация
Войти
/
dieoska
/
ddnet
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/export_settings_commands_table.py
222 строки
8 KB
heinrich5991
Format the world with ruff
17 дек 2025, 17:07
17 дек 2025, 17:07
3788112
Код
Авторство
О чём код?
#!/usr/bin/env python3 import csv from glob import glob import html import io import re import sys def parse_arguments(arguments_line, num, name): try: arguments_line = arguments_line.replace("\\n", "\\\\n") parsed = next(csv.reader(io.StringIO(arguments_line), skipinitialspace=True, escapechar="\\")) if len(parsed) != num: raise RuntimeError(f"Failed to parse {name} arguments: {arguments_line}, got {parsed}") return parsed except Exception as e: raise RuntimeError(f"Failed to parse {name} arguments: {arguments_line}") from e def parse_commands(lines): parsed_commands = [] for line in lines: args = re.match(r".*Register\((.*)\);", line) if args is None or "CFGFLAG_" not in args[1]: continue parsed = parse_arguments(args[1], num=6, name="command") parsed_commands.append({"name": parsed[0], "flags": parsed[2], "arguments": parsed[1], "description": parsed[5]}) parsed_commands.sort(key=lambda command: command["name"]) return parsed_commands def parse_settings(lines): def parse_value(value): value = value.lstrip() value = value.replace("SERVERINFO_LEVEL_MIN", "0") value = value.replace("SERVERINFO_LEVEL_MAX", "2") value = value.replace("SERVER_MAX_CLIENTS", "64") value = value.replace("MAX_CLIENTS", "128") try: return int(value, base=16) if value.startswith(("0x", "0X")) else int(value) except Exception as e: raise RuntimeError("Failed to parse setting value: {value}") from e names = {} parsed_settings = [] for line in lines: args = re.match(r"^MACRO_CONFIG_(.*?)\((.*)\)", line) if args is None: continue if args[1] == "STR": parsed = parse_arguments(args[2], num=6, name="string setting") flags = parsed[4] setting = {"type": "string", "flags": flags, "name": parsed[1], "description": parsed[5], "default": f'"{parsed[3]}"'} elif args[1] == "INT": parsed = parse_arguments(args[2], num=7, name="int setting") flags = parsed[5] setting = {"type": "int", "flags": flags, "name": parsed[1], "description": parsed[6], "default": parse_value(parsed[2]), "min": parse_value(parsed[3]), "max": parse_value(parsed[4])} elif args[1] == "COL": parsed = parse_arguments(args[2], num=5, name="color setting") flags = parsed[3] setting = {"type": "color", "flags": flags, "name": parsed[1], "description": parsed[4], "default": parse_value(parsed[2])} else: raise RuntimeError(f"Failed to parse settings type: {args[1]}") # Filter duplicates because config_variables.h contains some definitions multiple times with #ifdef if setting["name"] in names: parsed_settings[names[setting["name"]]] = setting else: names[setting["name"]] = len(parsed_settings) parsed_settings.append(setting) parsed_settings.sort(key=lambda setting: setting["name"]) return parsed_settings def parse_tunings(lines): def parse_value(value): value = value.strip() try: if value.endswith("f / SERVER_TICK_SPEED"): return float(value.rstrip("f / SERVER_TICK_SPEED")) / 50.0 return float(value.strip("f")) except Exception as e: raise RuntimeError("Failed to parse tuning value: {value}") from e parsed_tunings = [] for line in lines: args = re.match(r"^MACRO_TUNING_PARAM\((.*)\)", line) if args is None: continue parsed = parse_arguments(args[1], num=4, name="tuning") parsed_tunings.append({"name": parsed[1], "description": parsed[3], "default": parse_value(parsed[2])}) parsed_tunings.sort(key=lambda tuning: tuning["name"]) return parsed_tunings def export_commands(parsed_commands): output = ['<div style="overflow-x: auto;"><table class="settingscommands">'] output.append(" <tr><th>Command</th><th>Arguments</th><th>Description</th></tr>") for command in parsed_commands: output.append(f" <tr><td>{html.escape(command['name'])}</td><td>{html.escape(command['arguments'])}</td><td>{html.escape(command['description'])}</td></tr>") output.append("</table></div>") return "\n".join(output) def export_settings(parsed_settings): def export_color_value(setting): alpha = "CFGFLAG_COLALPHA" in setting["flags"] if "CFGFLAG_COLLIGHT7" in setting["flags"]: darkest_lgt = 61.0 / 255.0 elif "CFGFLAG_COLLIGHT" in setting["flags"]: darkest_lgt = 0.5 else: darkest_lgt = 0.0 number = setting["default"] a = ((number >> 24) & 0xFF) / 255.0 if alpha else 1.0 h = ((number >> 16) & 0xFF) / 255.0 s = ((number >> 8) & 0xFF) / 255.0 l = (number & 0xFF) / 255.0 l = darkest_lgt + l * (1.0 - darkest_lgt) return f'<span class="colorpreview" style="background: hsla({h * 360.0:.3f}, {s * 100.0:.3f}%, {l * 100.0:.3f}%, {a:.3f})"></span> {number}' output = ['<div style="overflow-x: auto;"><table class="settingscommands">'] output.append(" <tr><th>Setting</th><th>Description</th><th>Default</th><th>Min</th><th>Max</th></tr>") for setting in parsed_settings: line = f" <tr><td>{html.escape(setting['name'])}</td><td>{html.escape(setting['description'])}</td>" if setting["type"] == "string": line = line + f"<td>{html.escape(setting['default'])}</td><td></td><td></td></tr>" elif setting["type"] == "int": line = line + f"<td>{setting['default']}</td><td>{setting['min']}</td><td>{setting['max']}</td></tr>" elif setting["type"] == "color": line = line + f"<td>{export_color_value(setting)}</td><td></td><td></td></tr>" else: raise RuntimeError(f"Unhandled setting type: {setting['type']}") output.append(line) output.append("</table></div>") return "\n".join(output) def export_tunings(parsed_tunings): output = ['<div style="overflow-x: auto;"><table class="settingscommands">'] output.append(" <tr><th>Tuning</th><th>Description</th><th>Default</th></tr>") for tuning in parsed_tunings: output.append(f" <tr><td>{html.escape(tuning['name'])}</td><td>{html.escape(tuning['description'])}</td><td>{tuning['default']}</td></tr>") output.append("</table></div>") return "\n".join(output) def export_block(title, content): print('<div class="block">') print(f'<h2 id="{title.lower().replace(" ", "-")}">{title}</h2>') print(content) print("</div>") def read_files(pattern): for file in glob(pattern, recursive=True): with open(file, "r", encoding="utf-8", errors="ignore") as f: yield from f def export_settings_commands_table(): print("<!-- DO NOT EDIT MANUALLY! THIS FILE IS AUTOMATICALLY GENERATED BY https://github.com/ddnet/ddnet/blob/master/scripts/export_settings_commands_table.py -->") settings = parse_settings(read_files("src/engine/shared/config_variables.h")) commands = parse_commands(read_files("src/**/*.cpp")) export_block( "Map Settings", export_settings([setting for setting in settings if "CFGFLAG_SERVER" in setting["flags"] and "CFGFLAG_GAME" in setting["flags"]]) + "\n" + export_commands([command for command in commands if "CFGFLAG_SERVER" in command["flags"] and "CFGFLAG_GAME" in command["flags"]]), ) export_block( "Server Settings", export_settings([setting for setting in settings if "CFGFLAG_SERVER" in setting["flags"]]), ) export_block( "Econ Settings", export_settings([setting for setting in settings if "CFGFLAG_ECON" in setting["flags"]]), ) export_block( "Server Commands", export_commands([command for command in commands if "CFGFLAG_SERVER" in command["flags"] and "CFGFLAG_CHAT" not in command["flags"]]), ) export_block( "Chat Commands", export_commands([command for command in commands if "CFGFLAG_SERVER" in command["flags"] and "CFGFLAG_CHAT" in command["flags"]]), ) export_block( "Client Settings", export_settings([setting for setting in settings if "CFGFLAG_CLIENT" in setting["flags"]]), ) export_block( "Client Commands", export_commands([command for command in commands if "CFGFLAG_CLIENT" in command["flags"]]), ) export_block( "Tunings", export_tunings(parse_tunings(read_files("src/game/tuning.h"))), ) if __name__ == "__main__": # Fix UTF-8 encoding of output stream when redirecting to file sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") export_settings_commands_table()