Przejdź do treści

olski.konfiguracja

Konfiguracja projektu: jeden plik, w którym projekt deklaruje coś olskiemu.

Sekcje są trzy i każda mówi o jednym projekcie, a nie o polszczyźnie: leksykon deklaruje odmianę słowa, którego słownik nie ma (olski/projekt.py), lematy mówi, których lematów projekt używa, a których nie używa wcale (olski/słownictwo.py), a osoby mówi, które z nich nazywają kogoś (olski/osoby.py). Plik jest jeden, bo projekt jest jedną rzeczą i szukanie go jest jedną regułą; leży w jego korzeniu, a nie w paczce, bo zainstalowany olski jest jeden dla wszystkich projektów naraz. Braku nie zgłasza się (znajdź).

Format jest TOML-em, bo czyta go biblioteka standardowa (tomllib od 3.11), więc ani składni, ani jej komunikatów o błędach nie pisze ten moduł. Nazwy sekcji i kluczy nie mają znaków diakrytycznych i mieć ich nie mogą: klucz nagi w TOML-u jest ASCII, a klucz cytowany zapraszałby do pomyłki. Jest to jedyne miejsce, w którym reguła o polskich nazwach z CLAUDE.md ustępuje formatowi.

Ten moduł zna sam plik i podział na sekcje. Co znaczy wpis w sekcji, wie ten, kto tę sekcję czyta, i on zgłasza swoje usterki: struktura jest tutaj, znaczenie tam.

NAZWA = 'olski.toml' module-attribute

LEKSYKON = 'leksykon' module-attribute

LEMATY = 'lematy' module-attribute

OSOBY = 'osoby' module-attribute

SEKCJE = (LEKSYKON, LEMATY, OSOBY) module-attribute

PLIK = znajdź() module-attribute

KONFIGURACJA = czytaj(PLIK) if PLIK else {} module-attribute

ZłaKonfiguracja

Bases: Exception

Konfiguracja, z której nie wychodzi deklaracja, którą obiecuje.

Wyjątek, a nie wpis pominięty, bo plik pisze się ręką i każda przyczyna jest w nim usterką: sekcja nazwana inaczej, klucz nazwany inaczej, wartość o innym kształcie. Ruch po zgłoszeniu jest za każdym razem ten sam, czyli poprawiony plik, i dlatego klasa jest jedna.

Od ZłyWpis w olski/projekt.py różni się pytaniem: tamten mówi, że z poprawnie napisanego wpisu nie wychodzi odmiana, którą on obiecuje.

Source code in olski/konfiguracja.py
48
49
50
51
52
53
54
55
56
57
58
class ZłaKonfiguracja(Exception):
    """Konfiguracja, z której nie wychodzi deklaracja, którą obiecuje.

    Wyjątek, a nie wpis pominięty, bo plik pisze się ręką i każda przyczyna jest
    w nim usterką: sekcja nazwana inaczej, klucz nazwany inaczej, wartość o innym
    kształcie. Ruch po zgłoszeniu jest za każdym razem ten sam, czyli poprawiony
    plik, i dlatego klasa jest jedna.

    Od ``ZłyWpis`` w ``olski/projekt.py`` różni się pytaniem: tamten mówi, że
    z poprawnie napisanego wpisu nie wychodzi odmiana, którą on obiecuje.
    """

znajdź(skąd=None)

Konfiguracja projektu, w którym olskiego uruchomiono; None, gdy jej nie ma.

Szuka się jej od katalogu roboczego w górę, bo projekt ma podkatalogi: kto woła olskiego spod docs/, deklaruje wciąż to samo, co spod korzenia.

Source code in olski/konfiguracja.py
61
62
63
64
65
66
67
68
69
70
71
72
def znajdź(skąd: Path | None = None) -> Path | None:
    """Konfiguracja projektu, w którym olskiego uruchomiono; ``None``, gdy jej nie ma.

    Szuka się jej od katalogu roboczego w górę, bo projekt ma podkatalogi:
    kto woła olskiego spod ``docs/``, deklaruje wciąż to samo, co spod korzenia.
    """
    katalog = (skąd or Path.cwd()).resolve()
    for miejsce in (katalog, *katalog.parents):
        kandydat = miejsce / NAZWA
        if kandydat.is_file():
            return kandydat
    return None

czytaj(path)

Konfiguracja z pliku, sprawdzona co do sekcji i niczego poza nimi.

Source code in olski/konfiguracja.py
75
76
77
78
79
80
81
82
83
84
85
86
87
def czytaj(path: Path) -> dict[str, Mapping[str, Any]]:
    """Konfiguracja z pliku, sprawdzona co do sekcji i niczego poza nimi."""
    with path.open("rb") as plik:
        wczytane = tomllib.load(plik)
    nieznane = sorted(set(wczytane) - set(SEKCJE))
    if nieznane:
        raise ZłaKonfiguracja(
            f"{path}: sekcjami są {', '.join(SEKCJE)}, a nie {', '.join(nieznane)}"
        )
    for nazwa, dane in wczytane.items():
        if not isinstance(dane, Mapping):
            raise ZłaKonfiguracja(f"{path}: sekcja {nazwa} ma być tablicą kluczy")
    return wczytane

sekcja(nazwa, klucze, konfiguracja=None)

Jedna sekcja konfiguracji, sprawdzona co do kluczy i niczego poza nimi.

Klucz nazwany inaczej zgłasza się tutaj, a nie u tego, kto sekcję czyta, bo literówka w kluczu jest usterką tego samego rodzaju co literówka w nazwie sekcji i czytelnik ma dostać ten sam komunikat.

Source code in olski/konfiguracja.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def sekcja(
    nazwa: str,
    klucze: tuple[str, ...],
    konfiguracja: Mapping[str, Mapping[str, Any]] | None = None,
) -> Mapping[str, Any]:
    """Jedna sekcja konfiguracji, sprawdzona co do kluczy i niczego poza nimi.

    Klucz nazwany inaczej zgłasza się tutaj, a nie u tego, kto sekcję czyta,
    bo literówka w kluczu jest usterką tego samego rodzaju co literówka w nazwie
    sekcji i czytelnik ma dostać ten sam komunikat.
    """
    dane = (KONFIGURACJA if konfiguracja is None else konfiguracja).get(nazwa, {})
    nieznane = sorted(set(dane) - set(klucze))
    if nieznane:
        raise ZłaKonfiguracja(
            f"sekcja {nazwa}: kluczami są {', '.join(klucze)}, a nie {', '.join(nieznane)}"
        )
    return dane

napisy(nazwa, klucz, dane)

Wartość klucza jako zbiór napisów; kształt inny zgłasza się.

Pytają o to dwa klucze sekcji lematy i pytałby każdy następny, więc warunek stoi tu raz.

Source code in olski/konfiguracja.py
110
111
112
113
114
115
116
117
118
119
def napisy(nazwa: str, klucz: str, dane: Mapping[str, Any]) -> frozenset[str]:
    """Wartość klucza jako zbiór napisów; kształt inny zgłasza się.

    Pytają o to dwa klucze sekcji ``lematy`` i pytałby każdy następny,
    więc warunek stoi tu raz.
    """
    wartość = dane.get(klucz, [])
    if not isinstance(wartość, list) or not all(isinstance(item, str) for item in wartość):
        raise ZłaKonfiguracja(f"sekcja {nazwa}: {klucz} ma być listą napisów")
    return frozenset(wartość)