RegSort

Скачать здесь, читать подробнее здесь.

Утилита эта для Windows, но без проблем работает и под WINE в Linux например как-то так: wine ~/.wine/drive_c/Program\ Files/RegSort.exe settings.ini. Отсортированную версию файла она создаёт рядом с исходным, так что переименовать исходный в settings.ini.bak, а settings_Sorted.ini в settings.ini придётся самостоятельно. Это неудобно, но проверенно работает.

sortini.py

Я не умею кодить, но вайбкодить мне никто не запрещал, так что ChatGPT дал мне вот такой скрипт. Он работает, но с оговорками. Сперва он создаёт .bak и только затем сортирует секции и ключи в файле. Если бэкап уже существует, он может быть затёрт, прошу иметь в виду. И как он работает с кодировками, отличными от UTF-8, я не знаю, хотя это вряд ли актуально. Если хотите исправить и дополнить, пожалуйста делайте это, только обязательно дайте мне знать, потому что сейчас этот скрипт так себе, а у меня не хватит ума довести до ума.

#!/usr/bin/env python3
#
# sortini.py — простая сортировка .ini
# Навайбкодил с помощью ChatGPT: Eoin Gairleog
# Назначение: создаёт .bak, сортирует секции по алфавиту внутри .ini, затем сортирует ключи внутри секций
# Лицензия: WTFPL
# Версия: 1.0 (the first and the last)
# Совместимость: Python 3.8+
 
import sys
from pathlib import Path
 
def process_ini(path: Path):
    backup = path.with_suffix(path.suffix + ".bak")
    backup.write_bytes(path.read_bytes())
 
    out = []
 
    # читаем вручную, без configparser (он ломает формат)
    raw = path.read_text(encoding="utf-8").splitlines()
 
    sections = {}
    current = None
    buf = []
 
    def flush():
        if current is not None:
            sections[current] = buf.copy()
 
    for line in raw:
        stripped = line.strip()
        if stripped.startswith("[") and stripped.endswith("]"):
            flush()
            current = stripped
            buf = []
        else:
            if current is None:
                # строки вне секций оставляем как есть
                out.append(line)
            else:
                buf.append(line)
    flush()
 
    # сортируем секции
    for sec in sorted(sections.keys(), key=str.lower):
        out.append(sec)
        body = sections[sec]
 
        keys = []
        rest = []
        for l in body:
            s = l.strip()
            if "=" in s and not s.startswith(";") and not s.startswith("#"):
                keys.append(l)
            else:
                rest.append(l)
 
        # сортируем только пары ключ=значение
        keys_sorted = sorted(keys, key=lambda x: x.split("=")[0].strip().lower())
 
        for l in keys_sorted:
            out.append(l)
        for l in rest:
            out.append(l)
 
    path.write_text("\n".join(out) + "\n", encoding="utf-8")
 
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("usage: sortini.py file1.ini [file2.ini ...]")
        sys.exit(1)
 
    for fp in sys.argv[1:]:
        process_ini(Path(fp))