Biblioteka standardowa

Biblioteka standardowa AlexScript to zbiór modułów pokrywających rzeczy, których prędzej czy później potrzebuje każdy program: matematykę, daty i czas, I/O na plikach, JSON i CSV, sieć, hashowanie, bezpieczną losowość. Każdy moduł jest zaimplementowany natywnie w Ruby i wystawiony jako zwykła klasa AlexScript — co oznacza pełną integrację z językiem (dziedziczenie, refleksja, dispatch metod) oraz świetną wydajność, ponieważ praca wykonuje się w skompilowanym kodzie Ruby.

Każdą bibliotekę wczytuje się przez import("nazwa") (bez prefiksu ścieżki, bez rozszerzenia .as):

import("czas")
import("plik")
import("json")

niech t = Czas.teraz()
niech tresc = Plik.czytaj("dane.json")
niech dane = Json.parsuj(tresc)

Większość klas biblioteki standardowej jest statyczna — to zbiory powiązanych funkcji, a nie klasy, z których tworzysz instancje. Wywołanie Csv.nowy(), Mat.nowy() czy Json.nowy() rzuca błąd uruchomieniowy w stylu Csv jest klasą statyczną i nie może być instancjonowana. Dwa istotne wyjątki to Czas i prymitywy sieciowe (SocketTcp, SerwerTcp), które reprezentują zasoby ze stanem i konstruuje się je normalnie.

Każda metoda w tej referencji jest zweryfikowana z kodem źródłowym — nazwy, listy argumentów, typy zwracane i opisane zachowania zgadzają się z implementacją. Tam, gdzie dokumentacja opisuje zarówno podstawowe, jak i zaawansowane użycie, najpierw pokazany jest przypadek podstawowy.

CSV

Biblioteka Csv obsługuje parsowanie i generowanie danych w formacie CSV (comma-separated values). Bazuje na standardowej bibliotece CSV Ruby’ego, więc radzi sobie ze wszystkimi przypadkami brzegowymi formatu, których można się spodziewać: polami w cudzysłowach, przecinkami osadzonymi wewnątrz pól, wartościami wieloliniowymi i niestandardowymi separatorami.

Dane CSV są reprezentowane w AlexScript na dwa sposoby — w zależności od tego, której funkcji użyjesz. Zwykłe funkcje zwracają tablicę tablic — każdy wiersz to tablica napisów, a wiersz nagłówka (jeśli jest) to po prostu pierwsza tablica. Warianty *_z_naglowkami zwracają tablicę obiektów, traktując pierwszy wiersz jako nazwy pól.

import("csv")

# Zwykłe parsowanie — tablica tablic
niech wiersze = Csv.parsuj("imie,wiek\nJan,30\nAna,25")
pokazl wiersze[0]    # ["imie", "wiek"]
pokazl wiersze[1]    # ["Jan", "30"]

# Z nagłówkami — tablica obiektów
niech ludzie = Csv.parsuj_z_naglowkami("imie,wiek\nJan,30\nAna,25")
pokazl ludzie[0]["imie"]    # "Jan"
pokazl ludzie[1]["wiek"]    # "25"

Zauważ, że wszystkie wartości CSV zwracane są jako napisy, nawet jeśli wyglądają jak liczby. Jeśli potrzebujesz danych liczbowych, konwertuj je jawnie przez .liczba().

W bibliotece jest tylko Csv — nie wciąga żadnych klas, które trzeba by konstruować. Csv.nowy() rzuca błąd.

Stałe Csv

Csv.SEPARATOR — domyślny separator, ",". Zwykle nie ma potrzeby odwoływania się do niego bezpośrednio; jest niejawnym ustawieniem domyślnym dla każdej metody.

Parsowanie napisów

Csv.parsuj(tekst) / Csv.parsuj(tekst, separator) — parsuje napis CSV do tablicy tablic. Każdy element zewnętrznej tablicy to jeden wiersz; każdy element tablicy wewnętrznej to jedno pole jako napis. Separator domyślnie to ",".

niech wiersze = Csv.parsuj("a,b,c\n1,2,3")
pokazl wiersze.dlg()    # 2
pokazl wiersze[0]       # ["a", "b", "c"]

# Niestandardowy separator
niech tab_sep = Csv.parsuj("a;b;c\n1;2;3", ";")
pokazl tab_sep[1][0]    # "1"

# Pusty napis zwraca pustą tablicę
pokazl Csv.parsuj("").dlg()    # 0

Csv.parsuj_linie(tekst) / Csv.parsuj_linie(tekst, separator) — parsuje pojedynczą linię do tablicy pól. W przeciwieństwie do parsuj, oczekuje dokładnie jednej linii i zwraca po prostu ten wiersz, a nie tablicę zawierającą jeden wiersz.

niech pola = Csv.parsuj_linie("x,y,z")
pokazl pola         # ["x", "y", "z"]
pokazl pola.dlg()   # 3

Parsowanie z nagłówkami

Csv.parsuj_z_naglowkami(tekst) / Csv.parsuj_z_naglowkami(tekst, separator) — traktuje pierwszy wiersz jako nazwy pól i zwraca tablicę obiektów. Każdy wiersz staje się obiektem, którego klucze to nazwy kolumn.

niech dane = Csv.parsuj_z_naglowkami("imie,wiek,miasto\nJan,30,Warszawa\nAna,25,Kraków")

pokazl dane.dlg()              # 2  (wiersz nagłówka jest skonsumowany, nie wlicza się)
pokazl dane[0]["imie"]         # "Jan"
pokazl dane[1]["miasto"]       # "Kraków"

# Iteracja w naturalny sposób
dla osoba w dane {
    pokazl "#{osoba["imie"]} z #{osoba["miasto"]}"
}

Tej formy zwykle używa się przy pracy ze strukturyzowanymi danymi.

Csv.naglowki(tekst) / Csv.naglowki(tekst, separator) — wyciąga sam wiersz nagłówka jako tablicę napisów, bez parsowania reszty danych.

niech naglowki = Csv.naglowki("kol1,kol2,kol3\na,b,c")
pokazl naglowki    # ["kol1", "kol2", "kol3"]

Przydatne przy zerkaniu na strukturę pliku, zanim zdecydujesz, jak go przetwarzać.

Czytanie z plików

Csv.parsuj_plik(sciezka) / Csv.parsuj_plik(sciezka, separator) — wczytuje i parsuje plik CSV. Zwraca tablicę tablic, tak samo jak parsuj.

niech wiersze = Csv.parsuj_plik("./dane.csv")
pokazl wiersze.dlg()

Csv.parsuj_plik_z_naglowkami(sciezka) / Csv.parsuj_plik_z_naglowkami(sciezka, separator) — plikowy odpowiednik parsuj_z_naglowkami. Zwraca tablicę obiektów.

niech ludzie = Csv.parsuj_plik_z_naglowkami("./osoby.csv")
dla osoba w ludzie {
    pokazl osoba["imie"]
}

Csv.naglowki_pliku(sciezka) / Csv.naglowki_pliku(sciezka, separator) — wyciąga sam wiersz nagłówka z pliku.

niech kolumny = Csv.naglowki_pliku("./dane.csv")
pokazl "Plik ma #{kolumny.dlg()} kolumn"

Generowanie napisów CSV

Csv.generuj(wiersze) / Csv.generuj(wiersze, separator) — zamienia tablicę tablic z powrotem w napis CSV. Dołączany jest końcowy znak nowej linii.

niech tekst = Csv.generuj([
    ["imie", "wiek"],
    ["Jan", "30"],
    ["Ana", "25"]
])
pokazl tekst
# imie,wiek
# Jan,30
# Ana,25

Csv.generuj_linie(tablica) / Csv.generuj_linie(tablica, separator) — generuje pojedynczą linię CSV z tablicy pól.

pokazl Csv.generuj_linie(["a", "b", "c"])    # "a,b,c\n"

Csv.generuj_z_naglowkami(naglowki, wiersze) / Csv.generuj_z_naglowkami(naglowki, wiersze, separator) — generuje napis CSV z jawnym wierszem nagłówka, po którym idą wiersze danych. Wygodne, gdy masz nagłówki i wiersze w osobnych zmiennych.

niech tekst = Csv.generuj_z_naglowkami(
    ["produkt", "cena"],
    [["Chleb", "5"], ["Mleko", "4"]]
)

Pisanie do plików

Csv.generuj_plik(sciezka, wiersze) / Csv.generuj_plik(sciezka, wiersze, separator) — zapisuje wiersze do pliku CSV. Zwraca liczbę zapisanych wierszy.

niech zapisane = Csv.generuj_plik("./out.csv", [
    ["imie", "wiek"],
    ["Jan", "30"]
])
pokazl "Zapisano #{zapisane} wierszy"

Csv.generuj_plik_z_naglowkami(sciezka, naglowki, wiersze) / Csv.generuj_plik_z_naglowkami(sciezka, naglowki, wiersze, separator) — zapisuje wiersz nagłówka, a po nim wiersze danych.

Csv.generuj_plik_z_naglowkami(
    "./osoby.csv",
    ["imie", "wiek"],
    [["Jan", "30"], ["Ana", "25"]]
)

Inspekcja i wyciąganie kolumn

Csv.liczba_wierszy(tekst) / Csv.liczba_wierszy(tekst, separator) — liczy wiersze w napisie CSV, włączając nagłówek, jeśli jest obecny.

pokazl Csv.liczba_wierszy("a,b\n1,2\n3,4")    # 3

Csv.liczba_kolumn(tekst) / Csv.liczba_kolumn(tekst, separator) — liczy kolumny na podstawie pierwszego wiersza.

pokazl Csv.liczba_kolumn("a,b,c\n1,2,3")    # 3

Csv.kolumna(tekst, nazwa) / Csv.kolumna(tekst, nazwa, separator) — wyciąga pojedynczą kolumnę po nazwie nagłówka. Zwraca tablicę wartości z tej kolumny w kolejności wierszy. Sam wiersz nagłówka jest skonsumowany.

niech wieki = Csv.kolumna("imie,wiek\nJan,30\nAna,25", "wiek")
pokazl wieki    # ["30", "25"]

Csv.kolumna_pliku(sciezka, nazwa) / Csv.kolumna_pliku(sciezka, nazwa, separator) — to samo, ale wczytuje z pliku.

niech ceny = Csv.kolumna_pliku("./produkty.csv", "cena")

Te dwie metody świetnie sprawdzają się w zadaniach typu „daj mi jeden kawałek danych z tego całego pliku” — szczególnie użyteczne, gdy interesuje Cię tylko jedna kolumna i nie chcesz ładować całego zbioru danych do pamięci tylko po to, żeby go potem przefiltrować.

Czas — daty i czas

Biblioteka Czas reprezentuje punkty w czasie. Wartość Czas łączy datę (rok, miesiąc, dzień) z porą dnia (godzina, minuta, sekunda, precyzja poniżej sekundy) i strefą czasową (UTC, lokalna lub jawne przesunięcie). Biblioteka jest cienkim opakowaniem nad ruby’owym Time — operacje są szybkie, API jest kompletne, a obsługa stref czasowych odpowiada temu, czego można się spodziewać po Rubym.

W przeciwieństwie do większości klas biblioteki standardowej, Czas można instancjonować — każda wartość czasu to instancja Czas z bogatym wsparciem metod.

import("czas")

niech teraz = Czas.teraz()
pokazl "Dziś jest #{teraz.nazwa_dnia_tygodnia()}"
pokazl "Rok: #{teraz.rok()}, miesiąc: #{teraz.miesiac()}"

Arytmetyka czasu zwraca nowe wartości Czas — oryginał nigdy nie jest modyfikowany. Łańcuchy metod działają naturalnie:

niech jutro_o_18 = Czas.teraz().dodaj_dni(1).dodaj_godziny(18 - Czas.teraz().godzina())

Konstruowanie wartości czasu

Jest kilka sposobów na utworzenie wartości Czas, w zależności od tego, czym dysponujesz.

Czas.teraz() — bieżący czas lokalny. Najprostszy i najczęściej używany punkt wejścia.

niech t = Czas.teraz()
pokazl t.rok()       # np. 2026

Czas.nowy() — to samo co teraz(), gdy wywołane bez argumentów. Z argumentami buduje czas z jawnych komponentów lub parsuje napis.

Formy argumentów:

Czas.nowy()                              # bieżący czas
Czas.nowy(2024)                          # 2024-01-01 00:00:00
Czas.nowy(2024, 6, 15)                   # 2024-06-15 00:00:00
Czas.nowy(2024, 6, 15, 14, 30, 45)       # 2024-06-15 14:30:45
Czas.nowy("2024-06-15 14:30:45")         # sparsowane z napisu

Brakujące komponenty domyślnie ustawiają się na początek swojego zakresu — miesiąc i dzień na 1, godzina/minuta/sekunda na 0.

Czas.utc(rok, ...) — konstruuje czas w UTC. Taka sama forma argumentów jak nowy, ale wynik jest w strefie UTC:

niech t = Czas.utc(2024, 1, 1, 12, 0, 0)
pokazl t.czy_utc()    # prawda
pokazl t.strefa()     # "UTC"

Czas.lokalny(rok, ...) — konstruuje czas w lokalnej strefie czasowej systemu. Taka sama forma argumentów.

niech t = Czas.lokalny(2024, 7, 4, 18, 0, 0)

Czas.z_timestampu(sekundy) / Czas.z_timestampu(sekundy, mikrosekundy) — buduje czas z uniksowego timestampu (sekundy od epoki, 1970-01-01 UTC). Argument może być liczbą całkowitą lub zmiennoprzecinkową.

niech t1 = Czas.z_timestampu(0)
pokazl t1.rok()    # 1970

niech t2 = Czas.z_timestampu(1700000000)
pokazl t2.rok()    # 2023

# Ujemne timestampy są poprawne (sprzed epoki)
niech t3 = Czas.z_timestampu(-86400)
pokazl t3.rok()    # 1969

Czas.parsuj(tekst) — parsuje czas z napisu, używając heurystycznej detekcji. Rozpoznaje większość typowych formatów — daty w stylu ISO, formaty RFC, daty rozdzielone ukośnikami itd.

niech t = Czas.parsuj("2024-08-20 09:15:30")
pokazl t.rok()       # 2024
pokazl t.miesiac()   # 8

Czas.parsuj_format(tekst, format) — parsuje z jawnym formatem w stylu strftime. Użyj tego, gdy format wejściowy jest niestandardowy lub gdy chcesz być rygorystyczny w tym, co akceptujesz.

niech t = Czas.parsuj_format("20/06/2024", "%d/%m/%Y")
pokazl t.dzien()      # 20
pokazl t.miesiac()    # 6

Czas.z_iso8601(tekst) — parsuje rygorystyczny timestamp ISO 8601.

niech t = Czas.z_iso8601("2024-06-15T14:30:00Z")

Czas.z_rfc2822(tekst) — parsuje timestamp RFC 2822 (format używany w nagłówkach e-mail).

Czas.z_httpdate(tekst) — parsuje timestamp w formacie HTTP-date (używany w nagłówkach HTTP, takich jak Last-Modified).

niech t = Czas.z_httpdate("Wed, 15 Jun 2024 14:30:00 GMT")

Odczyt komponentów

Te metody odczytują poszczególne części wartości czasu. Żadna nie modyfikuje odbiorcy.

Metoda Zwraca
t.rok() rok, np. 2024
t.miesiac() miesiąc 1–12
t.dzien() dzień miesiąca 1–31
t.godzina() godzina 0–23
t.minuta() minuta 0–59
t.sekunda() sekunda 0–59 (lub 60 podczas sekundy przestępnej)
t.mikrosekunda() mikrosekundy w obrębie sekundy, 0–999999
t.nanosekunda() nanosekundy w obrębie sekundy
t.ulamek_sekundy() część poniżej sekundy jako liczba zmiennoprzecinkowa (np. 0,123)
t.dzien_tygodnia() dzień tygodnia 0–6 (niedziela = 0)
t.dzien_roku() dzień roku 1–366
t.strefa() nazwa strefy czasowej jako napis, np. "UTC" lub "CET"
t.przesuniecie_utc() przesunięcie względem UTC w sekundach
t.timestamp() uniksowy timestamp jako liczba całkowita (pełne sekundy)
t.timestamp_f() uniksowy timestamp jako liczba zmiennoprzecinkowa (z precyzją poniżej sekundy)
niech t = Czas.nowy(2024, 3, 14, 15, 9, 26)

pokazl t.rok()             # 2024
pokazl t.miesiac()         # 3
pokazl t.dzien()           # 14
pokazl t.dzien_roku()      # 74  (14 marca w roku przestępnym)
pokazl t.dzien_tygodnia()  # 4   (czwartek)
pokazl t.timestamp()       # np. 1710432566

Predykaty dnia tygodnia

Każdy z siedmiu dni ma dedykowany predykat zwracający wartość logiczną:

Metoda Zwraca prawda, gdy czas wypada…
t.czy_poniedzialek() w poniedziałek
t.czy_wtorek() we wtorek
t.czy_sroda() w środę
t.czy_czwartek() w czwartek
t.czy_piatek() w piątek
t.czy_sobota() w sobotę
t.czy_niedziela() w niedzielę
niech t = Czas.nowy(2024, 3, 14)    # czwartek
pokazl t.czy_czwartek()    # prawda
pokazl t.czy_poniedzialek() # falsz

Predykaty stanu

t.czy_utc()prawda, jeśli czas jest w UTC.

t.czy_czas_letni()prawda, jeśli w danej strefie i danym momencie obowiązuje czas letni.

niech t = Czas.utc(2024, 1, 1)
pokazl t.czy_utc()    # prawda

Arytmetyka czasu

Wszystkie metody arytmetyczne zwracają nową wartość Czas — odbiorca nigdy nie jest modyfikowany.

t.dodaj(sekundy) — dodaje sekundy. Wartości ujemne cofają w czasie.

niech t = Czas.nowy(2024, 1, 1, 12, 0, 0)
niech za_minute = t.dodaj(60)
niech minute_temu = t.dodaj(-60)

pokazl za_minute.minuta()    # 1
pokazl minute_temu.minuta()  # 59  (a godzina cofnęła się do 11)

t.odejmij(arg) — przeciążona w zależności od typu argumentu:

niech t1 = Czas.nowy(2024, 1, 1, 12, 0, 0)
niech t2 = Czas.nowy(2024, 1, 1, 13, 0, 0)

pokazl t2.odejmij(t1)     # 3600.0  (godzina, w sekundach)
pokazl t1.odejmij(60)     # wartość Czas, minutę wcześniej

Wygodne metody dodawania — każda przyjmuje liczbę i zwraca nowy czas przesunięty o tyle nazwanych jednostek do przodu:

Metoda Efekt
t.dodaj_sekundy(n) t + n sekund
t.dodaj_minuty(n) t + n * 60 sekund
t.dodaj_godziny(n) t + n * 3600 sekund
t.dodaj_dni(n) t + n * 86400 sekund
t.dodaj_tygodnie(n) t + n * 604800 sekund

Wygodne metody odejmowania — każda przyjmuje liczbę i zwraca nowy czas przesunięty wstecz:

Metoda Efekt
t.odejmij_sekundy(n) t - n sekund
t.odejmij_minuty(n) t - n * 60 sekund
t.odejmij_godziny(n) t - n * 3600 sekund
t.odejmij_dni(n) t - n * 86400 sekund
t.odejmij_tygodnie(n) t - n * 604800 sekund

Dobrze się komponują — łącz je dla naturalnie czytelnych wyrażeń:

niech termin = Czas.teraz()
    .dodaj_dni(7)
    .dodaj_godziny(3)
    .dodaj_minuty(30)

Warto znać jedną subtelność: arytmetyka miesięcy i lat nie jest częścią tego zestawu, ponieważ miesiące mają zmienną długość. Jeśli dodasz 30 dni do 31 stycznia, dostaniesz 2 marca, a nie „31 lutego”. Dla arytmetyki kalendarzowej w granulacji miesięcy/lat zbuduj ją sam, modyfikując rok() i miesiac() i konstruując nowy czas.

t.roznica(inny) — jawna różnica w sekundach zwrócona jako liczba zmiennoprzecinkowa. Równoważne t.odejmij(inny), gdy inny jest innym Czas, ale lepiej oddaje intencję, gdy taka właśnie jest:

niech ile_sek = pozniej.roznica(wczesniej)

Porównywanie

t.porownaj(inny) — porównanie trójwartościowe. Zwraca -1, jeśli t < inny, 0 jeśli równe, 1 jeśli t > inny.

niech t1 = Czas.nowy(2024, 1, 1)
niech t2 = Czas.nowy(2024, 12, 31)

pokazl t1.porownaj(t2)    # -1
pokazl t2.porownaj(t1)    # 1
pokazl t1.porownaj(t1)    # 0

t.rowny(inny) — ścisła równość (musi się zgadzać aż do nanosekund i strefy czasowej).

t.przed(inny)prawda, jeśli t jest wcześniejszy niż inny.

t.po(inny)prawda, jeśli t jest późniejszy niż inny.

jesli teraz.po(termin) {
    pokazl "Termin minął"
}

t.miedzy(od, do_czasu)prawda, jeśli t mieści się między od a do_czasu, włącznie z oboma końcami.

niech srodek = Czas.nowy(2024, 6, 15)
niech start = Czas.nowy(2024, 1, 1)
niech koniec = Czas.nowy(2024, 12, 31)

pokazl srodek.miedzy(start, koniec)    # prawda

Formatowanie i konwersja

t.format(wzor) — formatowanie w stylu strftime. Bez argumentów domyślnie "%Y-%m-%d %H:%M:%S".

niech t = Czas.nowy(2024, 3, 15, 14, 30, 45)

pokazl t.format("%Y-%m-%d")          # "2024-03-15"
pokazl t.format("%H:%M:%S")          # "14:30:45"
pokazl t.format("%d.%m.%Y %H:%M")    # "15.03.2024 14:30"

Dyrektywy formatu odpowiadają ruby’owemu strftime%Y dla czterocyfrowego roku, %m dla miesiąca z wiodącym zerem, %d dla dnia z wiodącym zerem, %H/%M/%S dla godziny/minuty/sekundy i wielu innych.

t.do_tekstu() — domyślny format czytelny dla człowieka z informacją o strefie czasowej, np. "2024-03-15 14:30:45 +0200".

t.ascii() — czas w formacie ASCII, np. "Fri Mar 15 14:30:45 2024".

t.iso8601() / t.iso8601(precyzja) — format ISO 8601. Przekaż liczbę całkowitą jako liczbę miejsc dziesiętnych po sekundzie.

niech t = Czas.utc(2024, 3, 15, 14, 30, 45)
pokazl t.iso8601()    # "2024-03-15T14:30:45Z"

t.rfc2822() — format RFC 2822 (używany w nagłówkach e-mail).

t.httpdate() — format HTTP-date (używany w Last-Modified i innych nagłówkach HTTP).

pokazl Czas.teraz().httpdate()
# np. "Sat, 15 Mar 2024 14:30:45 GMT"

t.do_utc() — konwertuje do UTC. Zwraca nowy czas w tym samym momencie, ale wyrażony w UTC.

t.do_lokalnego() / t.do_lokalnego(przesuniecie) — konwertuje do czasu lokalnego lub do jawnego przesunięcia.

t.do_strefy(przesuniecie) — konwertuje do określonego przesunięcia strefy czasowej, np. "+09:00" lub "-05:00".

niech t = Czas.utc(2024, 1, 1, 12, 0, 0)
niech tokyo = t.do_strefy("+09:00")
pokazl tokyo.godzina()    # 21

Zaokrąglanie

t.zaokraglij(precyzja) / t.zaokraglij() — zaokrąglij do najbliższej zadanej precyzji poniżej sekundy. Domyślna precyzja to 0 (pełne sekundy).

t.sufit(precyzja) / t.sufit() — zaokrąglij w górę (w stronę przyszłości).

t.podloga(precyzja) / t.podloga() — zaokrąglij w dół (w stronę przeszłości).

niech t = Czas.z_timestampu(1700000000.7)

pokazl t.podloga(0).sekunda()      # zaokrąglone w dół do pełnej sekundy
pokazl t.sufit(0).sekunda()        # zaokrąglone w górę

Dekompozycja

t.do_tablicy() — zwraca 10-elementową tablicę [sekunda, minuta, godzina, dzien, miesiac, rok, dzien_tygodnia, dzien_roku, czy_czas_letni, strefa]. Przydatne, gdy chcesz mieć wszystkie komponenty naraz do dalszego przetwarzania.

niech t = Czas.utc(2024, 3, 14, 15, 9, 26)
niech tab = t.do_tablicy()
pokazl tab[5]    # 2024  (rok)
pokazl tab[4]    # 3     (miesiąc)

Polskie nazwy

Dla naturalnego polskiego formatowania kilka metod zwraca dzień tygodnia lub nazwę miesiąca po polsku.

t.nazwa_dnia_tygodnia() — pełna polska nazwa dnia, np. "czwartek".

t.nazwa_dnia_skrot() — trzyliterowy polski skrót, np. "czw".

t.nazwa_miesiaca() — pełna polska nazwa miesiąca w mianowniku, np. "marzec".

t.nazwa_miesiaca_dopelniacz() — polska nazwa miesiąca w dopełniaczu (używana, gdy miesiąc kwalifikuje datę), np. "marca" (jak w 14 marca).

t.nazwa_miesiaca_skrot() — trzyliterowy polski skrót, np. "mar".

t.do_tekstu_pl() — pełny napis sformatowany po polsku, łączący nazwę dnia, dzień miesiąca, nazwę miesiąca w dopełniaczu, rok i godzinę.

niech t = Czas.nowy(2024, 3, 14, 15, 9, 26)
pokazl t.do_tekstu_pl()
# "czwartek, 14 marca 2024, 15:09:26"

Statyczne pomocniki

Czas.stempel() — bieżący uniksowy timestamp jako liczba całkowita. Równoważne Czas.teraz().timestamp(), ale nieco szybsze, ponieważ nie alokuje obiektu Czas.

niech ts = Czas.stempel()    # np. 1742990400

Czas.stempel_f() — bieżący uniksowy timestamp jako liczba zmiennoprzecinkowa, z precyzją poniżej sekundy.

niech start = Czas.stempel_f()
# ... zrób trochę pracy ...
niech elapsed = Czas.stempel_f() - start
pokazl "Trwało #{elapsed}s"

To standardowy idiom mierzenia upływu czasu zegarowego.

Czas.uspij(sekundy) — uśpij na zadaną liczbę sekund. Akceptuje liczby całkowite i zmiennoprzecinkowe. Blokuje bieżący wątek; w kodzie asynchronicznym lepiej używaj uspij(ms) (globalny builtin asynchroniczny), który jest kooperatywny.

Czas.uspij(0.5)    # pół sekundy
Czas.uspij(2)      # dwie sekundy

Digest — kryptograficzne funkcje skrótu

Biblioteka Digest oblicza kryptograficzne skróty (digests) — odciski palca o stałym rozmiarze dla dowolnych danych wejściowych. Skróty służą do weryfikacji integralności plików, porównywania dużych obiektów bez bezpośredniego porównywania ich zawartości, budowania HMAC-ów do uwierzytelniania wiadomości i wielu innych celów.

Digest jest klasą statyczną — nie tworzysz jej instancji; wywołujesz statyczne metody bezpośrednio:

import("digest")

niech h = Digest.sha256("Hello, World!")
pokazl h    # "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"

Wspieranych jest pięć algorytmów: MD5, SHA1, SHA256, SHA384 i SHA512. Dla każdego dostępne są cztery formaty wyjścia: napis hex, napis base64, surowa tablica bajtów oraz skrót pliku. Biblioteka udostępnia też funkcje HMAC, pomocnik do porównań stałoczasowych oraz narzędzia do konwersji między napisami hex a tablicami bajtów.

Uwaga o wyborze algorytmu: MD5 i SHA1 nie są bezpieczne do celów kryptograficznych (weryfikowanie autentyczności, hashowanie haseł, podpisywanie). Używaj ich wyłącznie do zastosowań nie-bezpiecznościowych, takich jak klucze cache, deduplikacja czy kompatybilność ze starszymi systemami. Do prac wrażliwych na bezpieczeństwo stosuj raczej SHA256 lub silniejsze.

Skróty hex

Najczęstsza forma. Zwraca napis hex małymi literami o stałej długości zależnej od algorytmu:

Metoda Długość wyjścia
Digest.md5(tekst) 32 znaki
Digest.sha1(tekst) 40 znaków
Digest.sha256(tekst) 64 znaki
Digest.sha384(tekst) 96 znaków
Digest.sha512(tekst) 128 znaków
pokazl Digest.md5("hello")     # "5d41402abc4b2a76b9719d911017c592"
pokazl Digest.sha256("hello")  # "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

Każda funkcja skrótu jest deterministyczna — to samo wejście zawsze daje to samo wyjście. To właśnie czyni je użytecznymi do wystawiania odcisków palca:

pokazl Digest.sha256("abc") == Digest.sha256("abc")    # prawda
pokazl Digest.sha256("abc") == Digest.sha256("xyz")    # falsz

Skróty base64

Czasem chcesz bardziej zwartej reprezentacji — base64 pakuje 8 bitów na znak, podczas gdy hex pakuje 4, więc wyjście jest mniej więcej dwa razy krótsze. Każdy algorytm ma wariant _base64:

Metoda Zwraca
Digest.md5_base64(tekst) MD5 zakodowane w base64
Digest.sha1_base64(tekst) SHA1 zakodowane w base64
Digest.sha256_base64(tekst) SHA256 zakodowane w base64
Digest.sha384_base64(tekst) SHA384 zakodowane w base64
Digest.sha512_base64(tekst) SHA512 zakodowane w base64
pokazl Digest.sha256_base64("hello")
# "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="

Surowe tablice bajtów

Dla aplikacji, które chcą surowy binarny skrót jako liczby — do serializacji w niestandardowym formacie, osadzania w protokole binarnym albo przekazania do innej operacji kryptograficznej. Każdy algorytm ma wariant _bajty zwracający tablicę wartości bajtowych (każdy 0–255):

Metoda Długość tablicy
Digest.md5_bajty(tekst) 16
Digest.sha1_bajty(tekst) 20
Digest.sha256_bajty(tekst) 32
Digest.sha384_bajty(tekst) 48
Digest.sha512_bajty(tekst) 64
niech bajty = Digest.sha256_bajty("hello")
pokazl bajty.dlg()    # 32
pokazl bajty[0]       # pierwszy bajt jako liczba całkowita 0–255

Skróty plików

Do haszowania zawartości pliku bezpośrednio, bez wcześniejszego wczytywania go do pamięci jako napisu. Przydatne dla dużych plików, gdzie wczytanie całej zawartości byłoby marnotrawstwem:

Metoda Zwraca
Digest.md5_plik(sciezka) hex MD5 zawartości pliku
Digest.sha1_plik(sciezka) hex SHA1
Digest.sha256_plik(sciezka) hex SHA256
Digest.sha384_plik(sciezka) hex SHA384
Digest.sha512_plik(sciezka) hex SHA512
niech checksum = Digest.sha256_plik("./large_archive.zip")
pokazl "Suma kontrolna: #{checksum}"

Wynik jest taki sam, jakbyś wczytał plik do napisu i wywołał na nim Digest.sha256, ale implementacja czyta plik kawałkami i używa stałej ilości pamięci niezależnie od rozmiaru pliku.

HMAC — uwierzytelnianie wiadomości kluczem

HMAC (Hash-based Message Authentication Code) łączy tajny klucz z wiadomością, produkując skrót, który dowodzi zarówno autentyczności, jak i integralności. Każdy, kto zna klucz, może zweryfikować HMAC; nikt bez klucza nie może go sfałszować dla nowej wiadomości.

Metoda Zwraca
Digest.hmac_md5(klucz, wiadomosc) hex HMAC-MD5 (32 znaki)
Digest.hmac_sha1(klucz, wiadomosc) hex HMAC-SHA1 (40 znaków)
Digest.hmac_sha256(klucz, wiadomosc) hex HMAC-SHA256 (64 znaki)
Digest.hmac_sha512(klucz, wiadomosc) hex HMAC-SHA512 (128 znaków)
niech klucz = "tajny-klucz-aplikacji"
niech podpis = Digest.hmac_sha256(klucz, "ważna wiadomość")
pokazl podpis    # 64-znakowy napis hex

Typowe zastosowanie to podpisywanie zapytań API:

funkcja podpisz_zapytanie(metoda, sciezka, body) {
    niech kanoniczne = "#{metoda}\n#{sciezka}\n#{body}"
    zwroc Digest.hmac_sha256(API_KLUCZ, kanoniczne)
}

Do HMAC-ów wrażliwych na bezpieczeństwo lepiej używaj hmac_sha256 lub hmac_sha512hmac_md5 i hmac_sha1 zostały zachowane wyłącznie dla kompatybilności ze starszymi systemami.

Bezpieczne porównywanie skrótów

Digest.porownaj(a, b) — porównuje dwa napisy (zwykle dwa skróty) w stałym czasie, zwracając prawda, jeśli się zgadzają, i falsz w przeciwnym wypadku.

Dlaczego osobna funkcja? Naiwne porównanie a == b może wyciekać informację przez timing — zwraca falsz, gdy tylko trafi na pierwszy różny bajt, więc atakujący mierzący czasy odpowiedzi mógłby odkrywać bajty kolejno. Porównanie stałoczasowe zawsze sprawdza wszystkie bajty, niezależnie od tego, gdzie jest pierwsza różnica, eliminując sygnał czasowy.

niech oczekiwany = Digest.hmac_sha256(klucz, wiadomosc)
niech otrzymany = naglowek_zapytania["X-Signature"]

jesli !Digest.porownaj(oczekiwany, otrzymany) {
    rzuc BladWykonania.nowy("Niepoprawny podpis")
}

Używaj Digest.porownaj zawsze gdy porównujesz podpisy HMAC, hashe haseł, tokeny sesji albo dowolne inne wartości tajne.

Narzędzia konwersji hex/bajty

Dwa pomocniki do przeskakiwania między napisami hex a tablicami bajtów — przydatne przy współpracy z systemami zewnętrznymi, które używają jednej formy, gdy Ty masz drugą.

Digest.hex_na_bajty(hex) — parsuje napis hex do tablicy wartości bajtowych. Każda para znaków hex staje się jednym bajtem.

niech bajty = Digest.hex_na_bajty("48656c6c6f")
pokazl bajty.dlg()    # 5
pokazl bajty[0]       # 72  (0x48 = 'H')

Digest.bajty_na_hex(bajty) — serializuje tablicę bajtów z powrotem do napisu hex małymi literami.

pokazl Digest.bajty_na_hex([72, 101, 108, 108, 111])    # "48656c6c6f"

Przejście tam i z powrotem przez obie funkcje zwraca oryginał — Digest.bajty_na_hex(Digest.hex_na_bajty(s)) jest równe s dla dowolnego poprawnego napisu hex.

Mat — matematyka

Biblioteka Mat udostępnia stałe i funkcje matematyczne — trygonometrię, logarytmy, pierwiastki, silnie, zaokrąglanie, liczby losowe oraz pomocniki teorii liczb, takie jak NWD i NWW. To cienkie opakowanie nad ruby’owym modułem Math plus garść dodatków specyficznych dla AlexScript.

Mat jest klasą statyczną. Wywołanie Mat.nowy() rzuca Mat jest klasą statyczną i nie może być instancjonowana.

import("mat")

pokazl Mat.PI                  # 3.141592653589793
pokazl Mat.sqrt(144)           # 12.0
pokazl Mat.sin(Mat.PI / 2)     # 1.0
pokazl Mat.silnia(10)          # 3628800
pokazl Mat.nwd(48, 18)         # 6

Funkcje trygonometryczne przyjmują i zwracają wartości w radianach — używaj Mat.na_radiany(stopnie) i Mat.na_stopnie(radiany) do konwersji. Większość funkcji zwraca wartości zmiennoprzecinkowe; operacje czysto na liczbach całkowitych (jak silnia, nwd, nww, dzielenie całkowite) zachowują typ całkowity.

Uwaga o nazewnictwie: nazwy w stylu identyfikatorów trzymają się konwencji używanej w innych miejscach AlexScript — wielkie litery dla stałych (Mat.PI, nie Mat.pi), polskie nazwy małymi literami dla metod. Krótkie angielskie nazwy uniwersalne w matematycznej notacji — sin, cos, log, sqrt, exp, gamma, frexp, ldexp, erf, erfc — zostały zachowane, ale operacje nazywane pojęciowo dostają polskie nazwy: Mat.podloga/Mat.sufit zamiast floor/ceil, Mat.zaokraglij zamiast round, Mat.silnia zamiast factorial, Mat.znak zamiast sign.

Stałe

Mat.PI — π, stosunek obwodu okręgu do jego średnicy, ≈ 3,14159.

Mat.E — liczba Eulera, podstawa logarytmu naturalnego, ≈ 2,71828.

Mat.NIESKONCZONOSC — dodatnia nieskończoność. Równa swoim własnym operacjom arytmetycznym: 1.0 / 0 daje tę wartość.

Mat.MINUS_NIESKONCZONOSC — ujemna nieskończoność, równoważne -Mat.NIESKONCZONOSC.

Mat.NAN — „not a number”, wynik niezdefiniowanych operacji takich jak 0.0 / 0.0. Zauważ, że Mat.NAN == Mat.NAN daje falsz zgodnie z regułami IEEE-754 — żeby sprawdzić, czy coś jest NaN-em, używaj Mat.czy_nan(x).

pokazl Mat.PI                       # 3.141592653589793
pokazl Mat.NIESKONCZONOSC > 1e100   # prawda
pokazl Mat.czy_nan(Mat.NAN)         # prawda

Trygonometria

Wszystkie funkcje trygonometryczne przyjmują i zwracają radiany.

Mat.sin(x) — sinus z x.

Mat.cos(x) — cosinus z x.

Mat.tan(x) — tangens z x.

pokazl Mat.sin(0)              # 0.0
pokazl Mat.sin(Mat.PI / 2)     # 1.0
pokazl Mat.cos(Mat.PI)         # -1.0
pokazl Mat.tan(Mat.PI / 4)     # 0.9999999999999999

Mat.asin(x) — arcus sinus. Dziedzina: -1 ≤ x ≤ 1. Przeciwdziedzina: -π/2 .. π/2.

Mat.acos(x) — arcus cosinus. Dziedzina: -1 ≤ x ≤ 1. Przeciwdziedzina: 0 .. π.

Mat.atan(x) — arcus tangens. Przeciwdziedzina: -π/2 .. π/2.

Mat.atan2(y, x) — dwuargumentowy arcus tangens. Zwraca kąt, którego tangens to y/x, biorąc pod uwagę znaki obu argumentów, żeby ustalić właściwą ćwiartkę. Przeciwdziedzina: -π .. π.

pokazl Mat.asin(1)         # 1.5707963267948966   (π/2)
pokazl Mat.atan2(1, 1)     # 0.7853981633974483   (π/4)
pokazl Mat.atan2(0, -1)    # 3.141592653589793    (π)

Funkcje hiperboliczne

Mat.sinh(x) — sinus hiperboliczny.

Mat.cosh(x) — cosinus hiperboliczny.

Mat.tanh(x) — tangens hiperboliczny.

Mat.asinh(x) — odwrotny sinus hiperboliczny.

Mat.acosh(x) — odwrotny cosinus hiperboliczny. Dziedzina: x ≥ 1.

Mat.atanh(x) — odwrotny tangens hiperboliczny. Dziedzina: -1 < x < 1.

pokazl Mat.sinh(0)    # 0.0
pokazl Mat.cosh(0)    # 1.0
pokazl Mat.tanh(0)    # 0.0

Funkcja wykładnicza i logarytmy

Mat.exp(x)e podniesione do potęgi x.

Mat.expm1(x)e^x - 1, obliczone z dodatkową precyzją dla małych x (gdzie bezpośrednie obliczenie traciłoby precyzję na rzecz kasowania zmiennoprzecinkowego).

pokazl Mat.exp(0)    # 1.0
pokazl Mat.exp(1)    # 2.718281828459045

Mat.log(x) / Mat.log(x, podstawa) — logarytm naturalny (przy podstawie e) lub logarytm o jawnej podstawie.

pokazl Mat.log(Mat.E)     # 1.0
pokazl Mat.log(8, 2)      # 3.0   logarytm o podstawie 2 z 8
pokazl Mat.log(1000, 10)  # 3.0   logarytm o podstawie 10 z 1000

Mat.log2(x) — logarytm o podstawie 2.

Mat.log10(x) — logarytm o podstawie 10.

Mat.log1p(x)log(1 + x), obliczone z dodatkową precyzją dla małych x.

pokazl Mat.log2(8)        # 3.0
pokazl Mat.log10(1000)    # 3.0

Potęga i pierwiastek

Mat.sqrt(x) — pierwiastek kwadratowy. Dziedzina: x ≥ 0.

Mat.cbrt(x) — pierwiastek sześcienny. Określony dla wszystkich liczb rzeczywistych (włącznie z ujemnymi).

Mat.hipotenuza(x, y)sqrt(x² + y²) obliczone bez przejściowego przekroczenia zakresu.

Mat.potega(x, y)x podniesione do potęgi y. Robi to samo co operator **, ale dostępne jako funkcja w przypadkach, gdy chcesz przekazać ją jako wartość.

pokazl Mat.sqrt(2)              # 1.4142135623730951
pokazl Mat.cbrt(27)             # 3.0
pokazl Mat.hipotenuza(3, 4)     # 5.0
pokazl Mat.potega(2, 10)        # 1024

Funkcje błędu i gamma

Mat.erf(x) — funkcja błędu, używana w prawdopodobieństwie i statystyce. Przeciwdziedzina: -1 .. 1.

Mat.erfc(x) — komplementarna funkcja błędu, równa 1 - erf(x), ale dokładniejsza dla dużych x.

Mat.gamma(x) — funkcja gamma, ciągłe rozszerzenie silni: gamma(n+1) == n! dla nieujemnych liczb całkowitych.

Mat.lgamma(x) — logarytm naturalny z wartości bezwzględnej gamma(x). Przydatne, gdy gamma(x) przekraczałaby zakres.

pokazl Mat.erf(0)        # 0.0
pokazl Mat.erfc(0)       # 1.0
pokazl Mat.gamma(5)      # 24.0    (4!)
pokazl Mat.gamma(1)      # 1.0     (0!)
pokazl Mat.lgamma(1)     # 0.0     (log(1) = 0)

Dekompozycja liczb zmiennoprzecinkowych

Mat.frexp(x) — rozkłada liczbę zmiennoprzecinkową na część ułamkową i wykładnik. Zwraca dwuelementową tablicę [ulamek, wykladnik] taką, że x == ulamek * 2^wykladnik. Przydatne do niskopoziomowej manipulacji liczbami zmiennoprzecinkowymi.

Mat.ldexp(ulamek, wykladnik) — odwrotność frexp. Zwraca ulamek * 2^wykladnik.

niech rozklad = Mat.frexp(1024.0)
pokazl rozklad                  # [0.5, 11]   1024 = 0.5 * 2^11
pokazl Mat.ldexp(0.5, 11)       # 1024.0

Zaokrąglanie

Mat.podloga(x) — podłoga: największa liczba całkowita ≤ x.

Mat.sufit(x) — sufit: najmniejsza liczba całkowita ≥ x.

Mat.zaokraglij(x) / Mat.zaokraglij(x, precyzja) — zaokrąglij do najbliższej liczby całkowitej lub do zadanej precyzji dziesiętnej.

Mat.obetnij(x) — obcięcie w stronę zera (odrzuca część ułamkową). Różni się od podloga dla liczb ujemnych: podloga(-3.7) daje -4, ale obetnij(-3.7) daje -3.

pokazl Mat.podloga(3.7)            # 3
pokazl Mat.podloga(-3.7)           # -4

pokazl Mat.sufit(3.2)              # 4
pokazl Mat.sufit(-3.7)             # -3

pokazl Mat.zaokraglij(3.5)         # 4
pokazl Mat.zaokraglij(3.14159, 2)  # 3.14

pokazl Mat.obetnij(3.9)            # 3
pokazl Mat.obetnij(-3.9)           # -3

Wartość bezwzględna i znak

Mat.abs(x) — wartość bezwzględna. Zachowuje typ — abs z liczby całkowitej daje liczbę całkowitą, abs z liczby zmiennoprzecinkowej daje liczbę zmiennoprzecinkową.

Mat.znak(x) — funkcja znaku. Zwraca 1, jeśli x > 0, -1, jeśli x < 0, 0, jeśli x == 0. Zawsze zwraca liczbę całkowitą.

pokazl Mat.abs(-5)        # 5
pokazl Mat.abs(-3.14)     # 3.14

pokazl Mat.znak(42)       # 1
pokazl Mat.znak(-7)       # -1
pokazl Mat.znak(0)        # 0

Min, max, ograniczenie

Mat.min(x, y) — mniejsza z dwóch wartości.

Mat.max(x, y) — większa z dwóch wartości.

Obie są ściśle dwuargumentoweMat.min(1, 2, 3) jest niepoprawne. Żeby znaleźć minimum lub maksimum tablicy, użyj metod arr.min() / arr.max() na tablicach numerycznych albo redukuj z Mat.min / Mat.max.

pokazl Mat.min(3, 7)      # 3
pokazl Mat.max(-1, 1)     # 1

# Dla tablic:
niech liczby = [5, 2, 9, 1, 7]
pokazl liczby.min()       # 1
pokazl liczby.max()       # 9

Mat.ogranicz(x, dolna, gorna) — ogranicza x do zakresu [dolna, gorna]. Zwraca dolna, jeśli x < dolna, gorna, jeśli x > gorna, w przeciwnym wypadku x.

pokazl Mat.ogranicz(5, 0, 10)     # 5
pokazl Mat.ogranicz(-5, 0, 10)    # 0
pokazl Mat.ogranicz(15, 0, 10)    # 10

Kombinatoryka

Mat.silnia(n) — silnia z nieujemnej liczby całkowitej n. Dla wartości ujemnej rzuca Silnia wymaga liczby nieujemnej.

pokazl Mat.silnia(0)     # 1
pokazl Mat.silnia(5)     # 120
pokazl Mat.silnia(10)    # 3628800
pokazl Mat.silnia(20)    # 2432902008176640000

Wynik jest liczbą całkowitą o dowolnej precyzji, więc nie musisz się martwić o przekroczenie zakresu dla dużych silni.

Konwersja stopnie–radiany

Mat.na_radiany(stopnie) — konwertuje stopnie na radiany.

Mat.na_stopnie(radiany) — konwertuje radiany na stopnie.

pokazl Mat.na_radiany(180)     # 3.141592653589793
pokazl Mat.na_stopnie(Mat.PI)  # 180.0

# Tam i z powrotem:
pokazl Mat.na_stopnie(Mat.na_radiany(45))   # 44.99999999999999

Liczby losowe

Te metody produkują niekryptograficzne liczby losowe — w sam raz do symulacji, próbkowania i gier, ale nie nadają się do celów bezpieczeństwa. Dla losowości kryptograficznej (hasła, tokeny, klucze, identyfikatory sesji) używaj biblioteki SecureRandom.

Mat.losowa() — losowa liczba zmiennoprzecinkowa w [0, 1).

Mat.losowa_zakres(od, do) — losowa liczba w przedziale obustronnie domkniętym [od, do]. Zwraca liczbę całkowitą, jeśli oba ograniczenia są całkowite, w przeciwnym razie zmiennoprzecinkową.

Mat.losowa_calkowita(od, do) — to samo co losowa_zakres. Udostępnione jako alias dla kodu, który wprost zaznacza, że oczekuje liczby całkowitej.

pokazl Mat.losowa()              # np. 0.7234819203748
pokazl Mat.losowa_zakres(1, 6)   # np. 4   (rzut kostką)
pokazl Mat.losowa_zakres(0, 100) # np. 73

Predykaty

Mat.czy_nan(x)prawda, jeśli x jest NaN-em. Jedyny poprawny sposób na sprawdzenie NaN-a — bezpośrednia równość (x == Mat.NAN) zawsze zwraca falsz zgodnie z regułami IEEE-754.

Mat.czy_nieskonczonosc(x)prawda, jeśli x to dodatnia lub ujemna nieskończoność.

Mat.czy_parzysta(n)prawda, jeśli liczba całkowita n jest parzysta.

Mat.czy_nieparzysta(n)prawda, jeśli liczba całkowita n jest nieparzysta.

pokazl Mat.czy_nan(Mat.NAN)                   # prawda
pokazl Mat.czy_nan(1.0)                       # falsz

pokazl Mat.czy_nieskonczonosc(Mat.NIESKONCZONOSC)  # prawda
pokazl Mat.czy_nieskonczonosc(1.0)            # falsz

pokazl Mat.czy_parzysta(4)                    # prawda
pokazl Mat.czy_nieparzysta(7)                 # prawda

Arytmetyka liczb całkowitych

Mat.nwd(a, b) — największy wspólny dzielnik dwóch liczb całkowitych.

Mat.nww(a, b) — najmniejsza wspólna wielokrotność dwóch liczb całkowitych.

Mat.reszta(a, b) — modulo, równoważne a % b. Udostępnione jako funkcja dla przypadków, gdy chcesz przekazać ją jako wartość.

Mat.dzielenie_calkowite(a, b) — dzielenie całkowite. Rzuca Dzielenie przez zero, jeśli b == 0.

pokazl Mat.nwd(12, 8)               # 4
pokazl Mat.nwd(17, 5)               # 1   (względnie pierwsze)
pokazl Mat.nww(4, 6)                # 12
pokazl Mat.nww(3, 7)                # 21

pokazl Mat.reszta(10, 3)            # 1
pokazl Mat.dzielenie_calkowite(10, 3)   # 3

Plik — wejście/wyjście systemu plików

Biblioteka Plik obsługuje wszystko, co związane z systemem plików: czytanie i zapisywanie plików, listowanie katalogów, sprawdzanie metadanych plików, pracę ze ścieżkami i przyrostowe I/O na otwartych uchwytach. Bazuje na ruby’owych klasach File, FileUtils i Dir, więc dziedziczy ich semantykę — w tym obsługę UTF-8, normalizację ścieżek i separatory specyficzne dla platformy.

Plik ma dwa tryby użycia. Najczęstszy to statyczne metody wygodne, które otwierają plik, wykonują operację i zamykają go za Ciebie:

import("plik")

# Wczytaj cały plik do napisu
niech tresc = Plik.czytaj("./dane.txt")

# Zapisz cały napis do pliku (zastępując istniejącą zawartość)
Plik.zapisz("./output.txt", "Witaj świecie")

# Dopisz na koniec
Plik.dopisz("./log.txt", "kolejna linia\n")

Drugi tryb to metody instancji wartości Plik, przydatne, gdy chcesz mieć drobniejszą kontrolę — przyrostowe czytanie, ustawianie pozycji, mieszanie odczytów i zapisów:

niech p = Plik.nowy("./duzy_plik.txt", "r")
niech pierwsza_linia = p.czytaj_linie()
p.zamknij()

Kilka metod istnieje w obu formach — czytaj, czytaj_linie, zapisz, zapisz_linie, rozmiar, czas_dostepu, czas_modyfikacji. Wersja statyczna przyjmuje napis ścieżki i obsługuje cykl otwarcia i zamknięcia; wersja instancji operuje na już otwartym Plik i daje Ci kontrolę nad tym, kiedy go zamknąć. Wybierz wariant odpowiedni do sytuacji.

Stałe

Plik.SEPARATOR — separator katalogów używany przez system operacyjny. "/" w systemach uniksowych, "\\" w Windows.

Plik.ALT_SEPARATOR — separator alternatywny w systemach, które go mają (Windows traktuje / jako alternatywę). Pusty napis na Uniksie.

Plik.PATH_SEPARATOR — separator używany między ścieżkami w zmiennych środowiskowych takich jak PATH. ":" na Uniksie, ";" w Windows.

pokazl Plik.SEPARATOR         # "/"   na Linux/macOS
pokazl Plik.PATH_SEPARATOR    # ":"   na Linux/macOS

Do budowania ścieżek przenośnie lepiej używaj Plik.polacz(...) — automatycznie dobiera odpowiedni separator.

Statyczne — czytanie

Plik.czytaj(sciezka) — wczytuje cały plik jako napis. Nadaje się do plików na tyle małych, że mieszczą się w pamięci.

niech tresc = Plik.czytaj("./README.md")
pokazl tresc.dlg()

Plik.czytaj_linie(sciezka) — wczytuje wszystkie linie jako tablicę napisów. Końcowe znaki nowej linii są usuwane z każdej linii.

niech linie = Plik.czytaj_linie("./konfiguracja.txt")
dla linia w linie {
    pokazl linia
}

Dla bardzo dużych plików, gdzie wolałbyś przetwarzać linia po linii bez wczytywania wszystkiego do pamięci, użyj formy instancji: Plik.nowy(sciezka, "r"), a następnie iteruj wywołaniami czytaj_linie() w pętli, albo użyj czytaj_linie_raw(), żeby zachować końcowe znaki nowej linii, jeśli ich potrzebujesz.

Statyczne — pisanie

Plik.zapisz(sciezka, tresc) — zapisuje tresc do pliku, zastępując istniejącą zawartość. Jeśli plik nie istnieje, jest tworzony. Jeśli katalog nadrzędny nie istnieje, wywołanie kończy się błędem.

Plik.dopisz(sciezka, tresc) — dopisuje tresc na koniec pliku, zachowując istniejącą zawartość. Tworzy plik, jeśli nie istnieje.

Plik.zapisz_linie(sciezka, linie) — zapisuje tablicę napisów jako osobne linie połączone znakami nowej linii (z końcowym znakiem nowej linii). Jeśli linie to pojedynczy napis, zostaje zapisany jako jedna linia.

Plik.zapisz("./out.txt", "linia 1\nlinia 2")
Plik.dopisz("./log.txt", "[INFO] start\n")

Plik.zapisz_linie("./lista.txt", ["alfa", "beta", "gamma"])
# Daje plik z trzema liniami: "alfa\nbeta\ngamma\n"

Statyczne — sprawdzanie ścieżek

Te metody sprawdzają właściwości ścieżki bez właściwego czytania zawartości pliku. Są tanie i nie zawodzą na nieistniejących ścieżkach — po prostu zwracają falsz.

Plik.istnieje(sciezka)prawda, jeśli cokolwiek (plik, katalog, link symboliczny) istnieje pod tą ścieżką.

Plik.czy_plik(sciezka)prawda, jeśli ścieżka prowadzi do zwykłego pliku (nie katalogu ani linku symbolicznego).

Plik.czy_katalog(sciezka)prawda, jeśli ścieżka prowadzi do katalogu.

Plik.czy_dowiazanie(sciezka)prawda, jeśli ścieżka prowadzi do linku symbolicznego.

Plik.czy_pusty(sciezka)prawda, jeśli plik ma zero bajtów albo katalog jest pusty. Zwraca prawda także dla nieistniejących ścieżek.

jesli Plik.istnieje("./dane.json") {
    niech d = Json.parsuj_plik("./dane.json")
}

jesli !Plik.czy_katalog("./output") {
    Plik.utworz_katalog("./output")
}

Plik.czy_odczytywalny(sciezka)prawda, jeśli bieżący proces może czytać ścieżkę.

Plik.czy_zapisywalny(sciezka)prawda, jeśli bieżący proces może pisać do ścieżki.

Plik.czy_wykonywalny(sciezka)prawda, jeśli ścieżka jest oznaczona jako wykonywalna.

Te metody sprawdzają efektywne uprawnienia, biorąc pod uwagę bity bieżącego użytkownika, grupy i „innych”.

Statyczne — komponenty ścieżki

Plik.nazwa(sciezka) / Plik.nazwa(sciezka, rozszerzenie) — nazwa pliku (ostatni komponent ścieżki). Jeśli podany jest drugi argument, ten przyrostek jest ucinany z wyniku.

pokazl Plik.nazwa("/home/anna/raport.pdf")          # "raport.pdf"
pokazl Plik.nazwa("/home/anna/raport.pdf", ".pdf")  # "raport"

Plik.katalog(sciezka) — część katalogowa ścieżki (wszystko przed ostatnim /).

pokazl Plik.katalog("/home/anna/raport.pdf")    # "/home/anna"
pokazl Plik.katalog("plik.txt")                 # "."

Plik.rozszerzenie(sciezka) — rozszerzenie pliku włącznie z wiodącą kropką, albo "", jeśli go nie ma.

pokazl Plik.rozszerzenie("raport.pdf")        # ".pdf"
pokazl Plik.rozszerzenie("README")            # ""
pokazl Plik.rozszerzenie(".bashrc")           # ""

Plik.pelna_sciezka(sciezka) — rozwija ~, rozwiązuje .. i ., i zwraca ścieżkę bezwzględną. Nie wymaga, żeby plik faktycznie istniał.

pokazl Plik.pelna_sciezka("./test")     # "/home/user/projekt/test"
pokazl Plik.pelna_sciezka("~/dane")     # "/home/user/dane"

Plik.rzeczywista_sciezka(sciezka) — jak pelna_sciezka, ale rozwiązuje też linki symboliczne. Ścieżka musi istnieć — w przeciwnym razie zawodzi.

Plik.polacz(...) — łączy komponenty ścieżki, używając separatora platformy. Wariadyczna: przyjmuje dowolną liczbę argumentów.

pokazl Plik.polacz("home", "anna", "dane.txt")     # "home/anna/dane.txt" na Uniksie
pokazl Plik.polacz(".", "src", "main.as")          # "./src/main.as"

Plik.podziel(sciezka) — dzieli ścieżkę na [katalog, nazwa_pliku] jako dwuelementową tablicę.

pokazl Plik.podziel("/var/log/app.log")    # ["/var/log", "app.log"]

Statyczne — metadane plików

Plik.rozmiar(sciezka) — rozmiar pliku w bajtach jako liczba całkowita.

niech rozm = Plik.rozmiar("./video.mp4")
pokazl "Plik ma #{rozm} bajtów"

Plik.czas_dostepu(sciezka) — czas ostatniego dostępu jako wartość Czas.

Plik.czas_modyfikacji(sciezka) — czas ostatniej modyfikacji jako wartość Czas.

Plik.czas_utworzenia(sciezka) — czas utworzenia pliku jako wartość Czas. Na systemach, które nie śledzą czasu utworzenia (większość starszych systemów plików), wraca do ctime (czas zmiany inode’a).

Plik.typ(sciezka) — typ pliku jako napis: "file", "directory", "link", "characterSpecial", "blockSpecial", "fifo", "socket" lub "unknown".

import("czas")
niech kiedy = Plik.czas_modyfikacji("./README.md")
pokazl "Ostatnia zmiana: #{kiedy.do_tekstu_pl()}"

pokazl Plik.typ("./README.md")    # "file"
pokazl Plik.typ("./src")          # "directory"

Statyczne — manipulacja plikami

Plik.usun(sciezka) — usuwa plik. Zawodzi, jeśli ścieżka prowadzi do katalogu (do tego użyj usun_katalog) albo nie istnieje. Zwraca prawda.

Plik.zmien_nazwe(stara, nowa) — zmienia nazwę pliku lub katalogu. Oba argumenty to ścieżki. Atomowe na tym samym systemie plików.

Plik.kopiuj(zrodlo, cel) — kopiuje plik. Nadpisuje cel, jeśli istnieje.

Plik.przesun(zrodlo, cel) — przenosi plik. Równoważne kopiowaniu i usunięciu, ale wydajniejsze na tym samym systemie plików.

Plik.kopiuj("./template.html", "./output/page.html")
Plik.zmien_nazwe("./tmp.dat", "./final.dat")
Plik.usun("./old_log.txt")

Plik.utworz_katalog(sciezka) — tworzy katalog razem ze wszystkimi brakującymi katalogami nadrzędnymi. Brak błędu, jeśli katalog już istnieje. Zwraca prawda.

Plik.usun_katalog(sciezka) — rekurencyjnie usuwa katalog i wszystko w nim. Używaj ostrożnie.

Plik.utworz_katalog("./output/raporty/2024")    # tworzy wszystkie trzy poziomy, jeśli trzeba
Plik.usun_katalog("./tmp")

Plik.zmien_uprawnienia(sciezka, tryb) — zmienia uprawnienia pliku na zadany uniksowy tryb (liczba całkowita typu 0o644 lub 420 dziesiętnie).

Statyczne — listowanie katalogów

Plik.lista(sciezka) / Plik.lista(sciezka, wzor) — listuje wpisy katalogu. Bez wzorca zwraca wszystko (z wyjątkiem . i ..). Z wzorcem glob ("*.txt", "raport_*" itd.) zwraca tylko dopasowania.

niech wszystkie = Plik.lista("./src")
niech tylko_as = Plik.lista("./src", "*.as")

dla plik w tylko_as {
    pokazl plik
}

Plik.lista_rekurencyjna(sciezka) / Plik.lista_rekurencyjna(sciezka, wzor) — jak lista, ale schodzi do podkatalogów. Wzorzec, jeśli podany, jest dopasowywany do pełnych ścieżek.

niech kazdy_as = Plik.lista_rekurencyjna(".", "*.as")
pokazl "Liczba plików .as: #{kazdy_as.dlg()}"

Statyczne — pliki tymczasowe i bieżący katalog

Plik.plik_tymczasowy() / Plik.plik_tymczasowy(prefiks) — zwraca ścieżkę świeżo utworzonego pliku tymczasowego. prefiks domyślnie to "alexscript" i pomaga zidentyfikować pochodzenie pliku.

Plik.katalog_tymczasowy() — zwraca ścieżkę systemowego katalogu tymczasowego (np. /tmp na Linuxie).

Plik.biezacy_katalog() — zwraca bieżący katalog roboczy.

Plik.zmien_katalog(sciezka) — zmienia katalog roboczy.

pokazl Plik.biezacy_katalog()         # "/home/anna/projekt"
Plik.zmien_katalog("./tests")
pokazl Plik.biezacy_katalog()         # "/home/anna/projekt/tests"

Metody instancji — otwieranie uchwytu

Plik.nowy(sciezka) / Plik.nowy(sciezka, tryb) — otwiera plik i zwraca instancję Plik. Tryb domyślnie to "r" (odczyt).

Typowe ciągi trybu:

niech p = Plik.nowy("./dane.txt", "r")
niech tresc = p.czytaj()
p.zamknij()

Ważne, żeby wywołać zamknij(), gdy skończysz — otwarte uchwyty plików to ograniczony zasób systemu. W przypadkach, gdy trudno to zagwarantować (wczesne zwroc, wyjątki), lepiej używaj statycznych metod wygodnych, które obsługują zamykanie automatycznie.

Instancja — czytanie

p.czytaj() / p.czytaj(n) — czyta resztę pliku albo następne n bajtów.

niech p = Plik.nowy("./binarny.dat", "rb")
niech naglowek = p.czytaj(16)     # pierwsze 16 bajtów
niech reszta = p.czytaj()          # cała reszta
p.zamknij()

p.czytaj_linie() — czyta wszystkie pozostałe linie jako tablicę napisów, z usuniętymi końcowymi znakami nowej linii.

p.czytaj_linie_raw() — to samo, ale zachowuje końcowe znaki nowej linii.

p.czytaj_bajty(n) — czyta następne n bajtów i zwraca je jako tablicę liczb całkowitych (0–255), nie jako napis. Przydatne dla danych binarnych.

p.czytaj_znak() — czyta i zwraca następny znak jako napis. Zwraca "" na końcu pliku.

niech p = Plik.nowy("./tekst.txt", "r")
niech znak = p.czytaj_znak()
dopoki znak != "" {
    pokaz znak
    znak = p.czytaj_znak()
}
p.zamknij()

Instancja — pisanie

p.zapisz(dane) — zapisuje napis do pliku w bieżącej pozycji.

p.zapisz_linie(linie) — zapisuje tablicę linii albo pojedynczą linię. Każdy wpis dostaje końcowy znak nowej linii.

p.wyczysc() — ucina plik do długości zero i przewija na początek. Przydatne we wzorcach typu „otwórz istniejący, zresetuj i zapisz na nowo”.

p.flush() — przesyła zbuforowane wyjście na dysk. Bez wymuszenia zapisu pisania mogą zostać w pamięci do zamknięcia pliku.

niech p = Plik.nowy("./log.txt", "a")
p.zapisz("nowy wpis\n")
p.flush()
p.zamknij()

Instancja — kursor i pozycja

p.przewin() — przesuwa kursor odczytu/zapisu na początek pliku.

p.przesun_kursor(pozycja) / p.przesun_kursor(pozycja, skad) — przesuwa kursor do określonej pozycji. Argument skad kontroluje punkt odniesienia: 0 (domyślnie) dla pozycji bezwzględnej od początku, 1 dla pozycji względem bieżącej, 2 dla pozycji względem końca.

p.pozycja() — zwraca bieżącą pozycję kursora w bajtach od początku.

niech p = Plik.nowy("./dane.txt", "r")
p.czytaj(100)
pokazl p.pozycja()       # 100
p.przewin()
pokazl p.pozycja()       # 0
p.zamknij()

Instancja — stan i metadane

p.zamknij() — zamyka plik. Idempotentne — wywołanie na już zamkniętym pliku jest w porządku.

p.czy_zamkniety()prawda, jeśli plik został zamknięty.

p.czy_koniec()prawda, jeśli kursor doszedł do końca pliku.

p.sciezka() — ścieżka, z którą plik został otwarty.

p.rozmiar() — bieżący rozmiar pliku w bajtach.

p.tryb() — tryb pliku jako napis ósemkowy (np. "100644").

p.czas_modyfikacji() — czas ostatniej modyfikacji jako wartość Czas.

p.czas_dostepu() — czas ostatniego dostępu jako wartość Czas.

niech p = Plik.nowy("./dane.txt", "r")
dopoki !p.czy_koniec() {
    niech linia = p.czytaj_linie()
    # przetwórz linię
    zakoncz       # używamy czytaj_linie, które czyta wszystko; tylko jedna iteracja
}
p.zamknij()

JSON

Biblioteka Json obsługuje parsowanie tekstu JSON do wartości AlexScript i serializację wartości AlexScript z powrotem do tekstu JSON. Bazuje na standardowym module JSON Ruby’ego — szybkim, dobrze przetestowanym i zgodnym ze standardem.

Mapowanie między JSON-em a AlexScriptem jest bezpośrednie: obiekty JSON stają się obiektami AlexScript, tablice JSON stają się tablicami, napisy/liczby/wartości logiczne JSON stają się ich odpowiednikami w AlexScript, a null z JSON-a staje się nic.

import("json")

# Sparsuj napis
niech dane = Json.parsuj("{\"imie\": \"Anna\", \"wiek\": 30}")
pokazl dane["imie"]      # "Anna"
pokazl dane["wiek"]      # 30

# Zserializuj wartość
niech json = Json.generuj({"klucz": "wartosc", "lista": [1, 2, 3]})
pokazl json              # {"klucz":"wartosc","lista":[1,2,3]}

Json jest klasą statyczną. Wywołanie Json.nowy() rzuca Json jest klasą statyczną i nie może być instancjonowana.

Uwaga o typach liczbowych: JSON ma tylko jeden typ liczbowy, ale AlexScript rozróżnia liczby całkowite od zmiennoprzecinkowych. Przejście tam i z powrotem przez JSON zachowuje tę różnicę — 42 parsuje się z powrotem do calkowita, a 42.0 parsuje się z powrotem do zmiennoprzecinkowa.

Parsowanie

Json.parsuj(tekst) — parsuje napis JSON. Wynik to wartość AlexScript odzwierciedlająca strukturę JSON-a: obiekty → obiekty, tablice → tablice, napisy → napisy, liczby → całkowite lub zmiennoprzecinkowe, wartości logiczne → prawda/falsz, nullnic.

# Obiekt
niech obj = Json.parsuj("{\"a\": 1, \"b\": [2, 3]}")
pokazl obj["a"]         # 1
pokazl obj["b"][0]      # 2

# Tablica
niech arr = Json.parsuj("[1, 2, 3]")
pokazl arr.dlg()        # 3

# Null i wartości logiczne
niech d = Json.parsuj("{\"t\": true, \"f\": false, \"n\": null}")
pokazl d["t"]           # prawda
pokazl d["f"]           # falsz
pokazl d["n"]           # nic

Jeśli wejście nie jest poprawnym JSON-em, Json.parsuj rzuca błąd uruchomieniowy. Żeby obsłużyć to elegancko, użyj parsuj_bezpiecznie (poniżej) albo opakuj wywołanie w proba/zlap.

Json.parsuj_bezpiecznie(tekst) — jak parsuj, ale zwraca nic zamiast rzucać przy niepoprawnym wejściu. Przydatne do parsowania „w razie czego”, gdy nie chcesz zobowiązywać się do obsługi błędów.

niech wynik = Json.parsuj_bezpiecznie(podejrzane_dane)
jesli wynik == nic {
    pokazl "To nie był poprawny JSON"
} albo {
    # użyj wynik
}

Json.parsuj_plik(sciezka) — wczytuje plik i parsuje go jako JSON. Równoważne Json.parsuj(Plik.czytaj(sciezka)), ale unika pośredniej zmiennej napisowej.

niech konfiguracja = Json.parsuj_plik("./config.json")
pokazl konfiguracja["wersja"]

Generowanie

Json.generuj(wartosc) — serializuje wartość AlexScript do zwięzłego JSON-a. Wyjście nie ma żadnej dodatkowej białej spacji — klucze, wartości i przecinki są ciasno upakowane.

pokazl Json.generuj({"a": 1, "b": "tekst"})
# {"a":1,"b":"tekst"}

pokazl Json.generuj([1, 2, [3, 4]])
# [1,2,[3,4]]

Json.generuj_ladnie(wartosc) / Json.generuj_ladnie(wartosc, wciecie) — JSON sformatowany czytelnie ze znakami nowej linii i wcięciami. Domyślne wcięcie to 2 spacje; przekaż liczbę całkowitą, żeby je zmienić.

pokazl Json.generuj_ladnie({"imie": "Anna", "wiek": 30})
# {
#   "imie": "Anna",
#   "wiek": 30
# }

pokazl Json.generuj_ladnie({"a": 1}, 4)    # 4-spacjowe wcięcie

Czytelnie sformatowane wyjście jest przyjazne dla człowieka, ale większe; zwięzłe wyjście jest właściwe do transmisji i przechowywania, gdzie liczy się każdy bajt.

Pisanie do plików

Json.generuj_plik(sciezka, wartosc) / Json.generuj_plik(sciezka, wartosc, ladnie) — serializuje wartość i zapisuje ją do pliku. Przekaż prawda jako trzeci argument, żeby uzyskać czytelnie sformatowane wyjście. Zwraca liczbę zapisanych bajtów.

niech dane = {"użytkownicy": [{"imie": "Jan", "wiek": 30}]}

# Zwięźle
Json.generuj_plik("./dane.json", dane)

# Czytelnie
Json.generuj_plik("./dane_pretty.json", dane, prawda)

Walidacja

Json.czy_poprawny(tekst)prawda, jeśli napis parsuje się jako poprawny JSON, w przeciwnym razie falsz. Nie rzuca przy niepoprawnym wejściu.

pokazl Json.czy_poprawny("{\"a\": 1}")    # prawda
pokazl Json.czy_poprawny("{a: 1}")        # falsz   (klucz bez cudzysłowów)
pokazl Json.czy_poprawny("nie json")      # falsz

Szybsze niż parsuj_bezpiecznie, jeśli chcesz tylko wiedzieć, czy napis jest poprawny, ponieważ nie musi budować wartości wynikowej.

Narzędzia do obiektów

Kilka pomocników do pracy ze sparsowanymi obiektami JSON. To wygody — to samo da się zrobić wbudowanymi metodami obiektów (.klucze(), .wartosci()).

Json.polacz(obj1, obj2) — łączy dwa obiekty. Klucze obecne w obj2 nadpisują te same klucze w obj1. Oba argumenty muszą być obiektami (nie tablicami ani skalarami) — w przeciwnym razie rzuca Oba argumenty muszą być obiektami.

niech a = {"x": 1, "y": 2}
niech b = {"y": 99, "z": 3}

pokazl Json.polacz(a, b)
# {"x": 1, "y": 99, "z": 3}

Json.klucze(obj) — zwraca tablicę kluczy obiektu. Argument musi być obiektem.

Json.wartosci(obj) — zwraca tablicę wartości obiektu w tej samej kolejności co klucze.

niech obj = {"a": 1, "b": 2, "c": 3}
pokazl Json.klucze(obj)      # ["a", "b", "c"]
pokazl Json.wartosci(obj)    # [1, 2, 3]

Te robią to samo co wbudowane metody .klucze() / .wartosci() na obiektach — istnieją na Json dla stylistycznej spójności, gdy już pracujesz z danymi pochodzącymi z JSON-a.

SecureRandom — kryptograficzna losowość

Biblioteka SecureRandom produkuje losowe wartości nadające się do zastosowań wrażliwych na bezpieczeństwo: haseł, tokenów sesji, kluczy API, jednorazowych liczb (nonce) do szyfrowania, unikalnych identyfikatorów. Bazuje na standardowym module SecureRandom Ruby’ego, który czerpie entropię z bezpiecznego źródła losowości systemu operacyjnego (/dev/urandom na Uniksie, CryptGenRandom w Windows).

Używaj SecureRandom zawsze gdy wartość ma być nieprzewidywalna dla atakującego. Do zastosowań nie-bezpieczeństwowych, gdzie potrzebujesz po prostu „jakiejś losowej liczby” — symulacje, próbkowanie, gry — używaj zamiast tego szybszej, ale niekryptograficznej rodziny Mat.losowa().

import("securerandom")

niech token_sesji = SecureRandom.token()      # 32-znakowy URL-safe token
niech id = SecureRandom.uuid()                 # standardowy UUID
niech klucz_api = SecureRandom.hex(32)         # 64-znakowy napis hex

SecureRandom jest klasą statyczną. Wywołanie SecureRandom.nowy() rzuca SecureRandom jest klasą statyczną — użyj SecureRandom.hex(), SecureRandom.uuid() itd..

Każda metoda produkuje świeżą losową wartość przy każdym wywołaniu — wyjścia są w praktyce gwarantowanie unikalne między wywołaniami w obrębie procesu i między procesami:

pokazl SecureRandom.uuid() != SecureRandom.uuid()    # prawda
pokazl SecureRandom.token() != SecureRandom.token()  # prawda

Napisy hex

SecureRandom.hex() / SecureRandom.hex(n) — losowy napis zakodowany w hex. Argument to liczba bajtów losowości; wynikowy napis jest dwa razy dłuższy, ponieważ każdy bajt to dwa znaki hex. Domyślnie 16 bajtów → 32 znaki hex.

pokazl SecureRandom.hex()        # 32-znakowy hex (16 bajtów losowości)
pokazl SecureRandom.hex(10)      # 20-znakowy hex (10 bajtów)
pokazl SecureRandom.hex(32)      # 64-znakowy hex (32 bajty — typowo dla kluczy)

Wyjście hex jest świetne, gdy potrzebujesz napisu bezpiecznego wszędzie — tylko cyfry i a-f, bez żadnych znaków specjalnych. Jest nieco mniej zwięzłe niż base64, ale prostota często jest tego warta.

Napisy base64

SecureRandom.base64()

/ SecureRandom.base64(n) — losowy napis zakodowany w base64. Argument to liczba bajtów; domyślnie 16. Wyjście może zawierać +, / i znaki dopełnienia =.

SecureRandom.urlsafe_base64() / SecureRandom.urlsafe_base64(n) — base64 z alfabetem URL-safe: + staje się -, / staje się _, brak dopełnienia =. Bezpieczne do bezpośredniego użycia w ścieżkach URL i parametrach zapytania.

niech zwykly = SecureRandom.base64(20)
niech bezpieczny = SecureRandom.urlsafe_base64(20)

pokazl bezpieczny.zawiera("+")    # falsz
pokazl bezpieczny.zawiera("/")    # falsz

Do tokenów, które będą osadzane w URL-ach lub nazwach plików, zawsze stosuj raczej urlsafe_base64 zamiast base64.

UUID-y

SecureRandom.uuid() — generuje UUID v4 (losowy). Zwraca 36-znakowy napis w standardowym formacie 8-4-4-4-12, np. "550e8400-e29b-41d4-a716-446655440000".

pokazl SecureRandom.uuid()
# np. "f47ac10b-58cc-4372-a567-0e02b2c3d479"

UUID-y są świetne do identyfikatorów, które muszą być unikalne bez koordynacji — kluczy głównych w bazach danych, identyfikatorów wiadomości w systemach rozproszonych, nazw plików, które nie zderzą się między maszynami.

Napisy alfanumeryczne

SecureRandom.alfanumeryczny() / SecureRandom.alfanumeryczny(n) — losowy napis z liter i cyfr o dokładnej długości n. Domyślne n to 16. Używa wielkich i małych liter oraz cyfr 0–9 (62 możliwe znaki na pozycję).

pokazl SecureRandom.alfanumeryczny()        # 16-znakowy napis
pokazl SecureRandom.alfanumeryczny(8)       # np. "Kp3Hx9Dq"
pokazl SecureRandom.alfanumeryczny(64)      # 64-znakowy napis

Przydatne, gdy potrzebujesz losowego napisu o dokładnej długości i chcesz uniknąć zmiennej długości wyjścia base64.

Wartości liczbowe

SecureRandom.losowa_liczba() / SecureRandom.losowa_liczba(n) — losowa liczba. Bez argumentu zwraca liczbę zmiennoprzecinkową w [0, 1). Z argumentem całkowitym n zwraca liczbę całkowitą w [0, n).

niech ulamek = SecureRandom.losowa_liczba()       # np. 0.7234819203
niech kostka = SecureRandom.losowa_liczba(6)      # 0..5 (sześciościenna)
niech wybor = SecureRandom.losowa_liczba(100)     # 0..99

SecureRandom.losowa_z_zakresu(min, max) — losowa liczba całkowita w obustronnie domkniętym zakresie [min, max].

pokazl SecureRandom.losowa_z_zakresu(1, 100)     # 1..100
pokazl SecureRandom.losowa_z_zakresu(5, 5)       # zawsze 5

Zwróć uwagę na różnicę z losowa_liczba(n): losowa_liczba(100) zwraca 0..99 (górna granica wyłączna), a losowa_z_zakresu(0, 100) zwraca 0..100 (oba końce włącznie).

Losowe bajty

SecureRandom.losowe_bajty() / SecureRandom.losowe_bajty(n) — tablica n losowych bajtów (każdy 0–255). Domyślne n to 16.

niech bajty = SecureRandom.losowe_bajty(16)
pokazl bajty.dlg()        # 16
pokazl bajty[0]           # jakaś liczba całkowita 0..255

Przydatne, gdy chcesz wpuścić losowe bity do binarnego protokołu, do funkcji wyprowadzania klucza szyfrującego albo do formatu plikowego — wszędzie tam, gdzie potrzebujesz surowych bajtów zamiast zakodowanych napisów.

Tokeny

SecureRandom.token() / SecureRandom.token(n) — token URL-safe o dokładnej długości n. Domyślne n to 32. Wewnętrznie generuje URL-safe base64 i tnie do żądanej długości.

niech ctoken = SecureRandom.token()       # 32-znakowy URL-safe
niech longer = SecureRandom.token(64)      # 64-znakowy URL-safe

Standardowy wybór do tokenów sesji, kluczy API, tokenów resetu hasła i podobnych — wystarczająco długie, żeby ich nie dało się zgadnąć, wystarczająco krótkie, żeby wygodnie mieściły się w nagłówkach HTTP i w URL-ach.

Niestandardowy alfabet

SecureRandom.wybierz(znaki, dlugosc) — generuje losowy napis o zadanej długości, używając tylko znaków z alfabetu znaki. Każda pozycja losowana jest jednorodnie z dostępnych znaków.

# Numeryczny PIN
pokazl SecureRandom.wybierz("0123456789", 6)        # np. "847219"

# Tylko małe litery
pokazl SecureRandom.wybierz("abcdefghijklmnopqrstuvwxyz", 10)
# np. "kjqxnvbpzm"

# Niestandardowy zestaw
pokazl SecureRandom.wybierz("ABCDEFGH123", 4)        # np. "C2FH"

Przydatne dla kodów przyjaznych dla człowieka (bez niejednoznacznych znaków typu 0/O albo l/1) i gdy musisz integrować się z systemami, które mają ograniczenia alfabetu.

Socket — sieć TCP i UDP

Biblioteka socket wystawia cztery klasy, które razem pokrywają większość potrzeb programowania sieciowego:

Wszystkie one integrują się ze schedulerem asynchronicznym AlexScript — gdy wywołujesz czytaj_linie() na gnieździe wewnątrz funkcji asynchronicznej, wywołanie kooperatywnie oddaje sterowanie zamiast blokować cały reaktor. Oznacza to, że możesz pisać prostą logikę obsługi zapytań, a ona skaluje się do wielu współbieżnych połączeń bez callbacków. Zobacz Asynchroniczność z natywnym I/O w głównym tutorialu.

import("socket")

# Szybki przykład: klient echo
niech kl = SocketTcp.nowy("127.0.0.1", 8080)
kl.wyslij_linie("witaj")
pokazl kl.czytaj_linie()
kl.zamknij()

Cztery klasy używają tego samego wzorca zamykania: s.zamknij() wyłącza gniazdo, s.czy_zamkniety() mówi, czy zostało zamknięte. Ważne, żeby zamykać każde otwarte gniazdo — to ograniczony zasób systemu operacyjnego.

SocketTcp — klient TCP

Połączone gniazdo TCP używane do rozmowy z serwerem TCP. Konstruuj go z hostem i portem; połączenie jest nawiązywane podczas konstrukcji.

SocketTcp.nowy(host, port) — łączy z host:port. Rzuca błąd uruchomieniowy przy niepowodzeniu połączenia (odmowa, timeout, błąd rozwiązania DNS).

niech kl = SocketTcp.nowy("example.com", 80)

SocketTcp możesz też dostać z wywołania SerwerTcp.akceptuj() — w ten sposób strona serwerowa otrzymuje uchwyt do rozmowy z połączonym klientem.

Wysyłanie danych

s.wyslij(dane) — zapisuje napis do gniazda. Zwraca liczbę zapisanych bajtów.

s.wyslij_linie(dane) — zapisuje napis z końcowym znakiem nowej linii. Zwraca łączną liczbę zapisanych bajtów (włącznie ze znakiem nowej linii).

kl.wyslij("GET / HTTP/1.0\r\n\r\n")
kl.wyslij_linie("PING")        # wysyła "PING\n"

s.flush() — natychmiast przesyła zbuforowane wyjście do sieci.

Odbieranie danych

s.odbierz(rozmiar) / s.odbierz() — czyta do rozmiar bajtów (domyślnie 4096) z gniazda. Zwraca to, co dotąd przyszło, co może być mniejsze niż rozmiar.

s.czytaj(n) — czyta dokładnie n bajtów (albo wszystkie pozostałe bajty, jeśli bez argumentu). Blokuje, dopóki nie pojawi się tyle danych albo połączenie nie zostanie zamknięte.

s.czytaj_linie() — czyta jedną linię z gniazda, ucinając końcowy znak nowej linii. Zwraca "", jeśli połączenie zostało zamknięte.

s.czytaj_wszystkie_linie() — czyta wszystkie pozostałe linie jako tablicę. Blokuje, dopóki druga strona nie zamknie strony zapisu.

niech naglowek = kl.odbierz(1024)
niech cale_cialo = kl.czytaj()
niech jedna_linia = kl.czytaj_linie()
niech wszystkie = kl.czytaj_wszystkie_linie()

Różnica między odbierz a czytaj ma znaczenie: odbierz zwraca to, co jest dostępne właśnie teraz (do limitu), a czytaj(n) czyta dalej, dopóki nie zbierze dokładnie n bajtów. Dla protokołów, gdzie parsujesz wiadomości znanej długości, używaj czytaj(n); dla strumieniowania, gdzie chcesz reagować na to, co przyszło, używaj odbierz.

Zamykanie i półzamykanie

s.zamknij() — zamyka oba kierunki gniazda. Idempotentne — wywołanie na już zamkniętym gnieździe jest w porządku.

s.zamknij_zapis() — zamyka tylko stronę zapisu. Druga strona zobaczy koniec pliku, ale Ty nadal możesz odbierać jej odpowiedź. Przydatne dla protokołów typu „skończyłem wysyłać; powiedz mi, kiedy Ty skończysz”.

s.zamknij_odczyt() — zamyka tylko stronę odczytu.

s.zamknij_odlozone() — planuje gniazdo do kooperatywnego zamknięcia przez reaktor asynchroniczny. Przydatne w kontekstach asynchronicznych, gdzie natychmiastowe zamknięcie mogłoby zaburzyć trwające I/O na innych fiberach.

s.czy_zamkniety()prawda, jeśli któryś z kierunków został zamknięty.

kl.wyslij_linie("ostatnia wiadomosc")
kl.zamknij_zapis()                  # powiedz drugiej stronie, że skończyliśmy
niech odpowiedz = kl.czytaj()       # ale nadal czytaj jej odpowiedź
kl.zamknij()

Informacje o adresie

s.adres_lokalny() — zwraca naszą stronę połączenia jako {"adres": "...", "port": N}.

s.adres_zdalny() — zwraca stronę drugiego końca połączenia w tym samym formacie.

niech moj = kl.adres_lokalny()
niech peer = kl.adres_zdalny()
pokazl "Polaczono z #{peer["adres"]}:#{peer["port"]}"

Opcje gniazda

s.ustaw_timeout(sekundy) — ustawia limity czasu wysyłania i odbierania na sekundy pełnych sekund. Po przekroczeniu limitu blokujące operacje kończą się błędem zamiast wisieć w nieskończoność.

s.ustaw_keepalive(wlacz) — włącza lub wyłącza sondy TCP keep-alive. Przekaż prawda, żeby włączyć, falsz, żeby wyłączyć. Przydatne dla długich połączeń, gdzie chcesz wykrywać martwych peerów.

s.ustaw_nodelay(wlacz) — włącza lub wyłącza algorytm Nagle’a (TCP_NODELAY). Przekaż prawda, żeby wyłączyć Nagle’a (niskie opóźnienia, więcej pakietów), falsz, żeby zostawić go włączonego (większa przepustowość, więcej buforowania).

kl.ustaw_timeout(30)
kl.ustaw_keepalive(prawda)
kl.ustaw_nodelay(prawda)        # dla protokołów interaktywnych

SerwerTcp — serwer TCP

Nasłuchujące gniazdo, które przyjmuje przychodzące połączenia TCP. Każde przyjęte połączenie staje się osobnym SocketTcp, z którego możesz niezależnie czytać i pisać.

SerwerTcp.nowy(port) / SerwerTcp.nowy(port, adres) — przypisuje się do portu i zaczyna nasłuchiwać. Domyślny adres to "0.0.0.0" (nasłuchuj na wszystkich interfejsach). Przekaż "127.0.0.1", żeby nasłuchiwać tylko na localhost. Konstruktor automatycznie ustawia SO_REUSEADDR, więc niedawno ubity serwer może natychmiast przypiąć się ponownie do tego samego portu.

niech srv = SerwerTcp.nowy(8080)              # wszystkie interfejsy
niech lokalny = SerwerTcp.nowy(8080, "127.0.0.1")    # tylko localhost

Przyjmowanie połączeń

s.akceptuj() — blokuje, dopóki klient się nie połączy, a następnie zwraca SocketTcp dla nowego połączenia.

niech srv = SerwerTcp.nowy(8080)

petla {
    niech klient = srv.akceptuj()
    niech zapytanie = klient.czytaj_linie()
    klient.wyslij_linie("Otrzymano: #{zapytanie}")
    klient.zamknij()
}

W kontekście asynchronicznym akceptuj() kooperatywnie oddaje sterowanie podczas czekania, dzięki czemu inne fibery mogą posuwać się do przodu.

Cykl życia serwera

s.zamknij() — zamyka nasłuchiwacz. Już nawiązane połączenia zostają otwarte; tylko nowe są odrzucane.

s.czy_zamkniety()prawda, jeśli serwer został zamknięty.

s.adres_lokalny() — zwraca {"adres": "...", "port": N} dla adresu nasłuchiwania.

s.port() — wygodny sposób na pobranie samego portu nasłuchiwania. Przydatne, gdy otworzyłeś na porcie 0 (przydzielonym przez kernel) i chcesz wiedzieć, co dostałeś.

s.ustaw_nasluchiwanie(max) — ustawia rozmiar kolejki oczekujących połączeń. Domyślna wartość systemu zwykle wystarcza, ale w serwerach o wysokiej przepustowości warto ją zwiększyć.

niech srv = SerwerTcp.nowy(0)            # niech kernel wybierze port
pokazl "Słucham na porcie #{srv.port()}"

Wbudowana pętla połączeń

s.uruchom_petle(callback) — startuje pętlę akceptacji, która obsługuje każde przychodzące połączenie we własnym wątku, wywołując callback(klient) dla każdego połączenia. Callback dostaje SocketTcp i odpowiada za czytanie, pisanie i zamykanie (zamknięcie i tak następuje automatycznie po wyjściu z callbacka).

To wyższy poziom abstrakcji niż ręczny wzorzec petla { srv.akceptuj() ... } — przydatne, gdy chcesz współbieżnej obsługi bez pisania kodu wątkowania samodzielnie.

funkcja obsluz(klient) {
    niech zapytanie = klient.czytaj_linie()
    klient.wyslij_linie("echo: #{zapytanie}")
}

niech srv = SerwerTcp.nowy(8080)
srv.uruchom_petle(obsluz)         # obsługuje połączenia współbieżnie

uruchom_petle blokuje wywołujący kod — raz uruchomione, działa, dopóki gniazdo serwera nie zostanie zamknięte.

SocketUdp — gniazdo UDP

UDP jest bezpołączeniowe — wysyłasz i odbierasz datagramy do/od dowolnych peerów bez wcześniejszego nawiązywania sesji. Pojedyncze SocketUdp może rozmawiać z wieloma peerami jednocześnie.

SocketUdp.nowy() — tworzy niepowiązane gniazdo UDP. Żeby odbierać, trzeba je zwiaz z portem; żeby tylko wysyłać, wiązanie nie jest potrzebne.

niech udp = SocketUdp.nowy()

Wiązanie do odbioru

s.zwiaz(port) / s.zwiaz(port, adres) — wiąże gniazdo z lokalnym portem (i opcjonalnie z konkretnym adresem lokalnym, domyślnie "0.0.0.0"), żeby mogło odbierać przychodzące datagramy.

niech srv = SocketUdp.nowy()
srv.zwiaz(5353)                  # nasłuchuj na UDP 5353, wszystkie interfejsy

Wysyłanie datagramów

s.wyslij(dane, host, port) — wysyła dane jako datagram do host:port. Każde wywołanie wysyła dokładnie jeden datagram.

s.polacz(host, port) — przypisuje domyślny cel, żeby kolejne wysyłki nie musiały go określać. W rzeczywistości nie nawiązuje połączenia (UDP go nie ma) — to wygoda.

s.wyslij_polaczony(dane) — wysyła datagram do wcześniej ustawionego przez polacz celu.

niech kl = SocketUdp.nowy()
kl.wyslij("ping", "127.0.0.1", 5353)

# Albo z domyślnym celem:
kl.polacz("127.0.0.1", 5353)
kl.wyslij_polaczony("ping1")
kl.wyslij_polaczony("ping2")

Odbieranie datagramów

s.odbierz(rozmiar) / s.odbierz() — czeka na następny datagram i go zwraca. Domyślny rozmiar to 4096; większe datagramy zostaną przycięte. Zwraca obiekt z trzema kluczami:

niech wiadomosc = srv.odbierz()
pokazl "Z #{wiadomosc["adres"]}:#{wiadomosc["port"]}: #{wiadomosc["dane"]}"

Zamykanie i sprawdzanie

s.zamknij() — zamyka gniazdo.

s.czy_zamkniety()prawda, jeśli zamknięte.

s.adres_lokalny() — zwraca {"adres": "...", "port": N} dla adresu, do którego się przypięło.

Socket — statyczne pomocniki

Sama klasa Socket (różna od SocketTcp itd.) udostępnia funkcje narzędziowe do rozwiązywania nazw hostów, sprawdzania IP i zarządzania portami. Jest statyczna — wywołanie Socket.nowy() rzuca Socket jest klasą statyczną — użyj SocketTcp, SerwerTcp lub SocketUdp.

Rozwiązywanie nazw hostów

Socket.nazwa_hosta() — zwraca nazwę hosta lokalnej maszyny.

Socket.pobierz_adres_ip(nazwa) — rozwiązuje nazwę hosta na pojedynczy adres IPv4 (pierwszy zwrócony przez DNS).

Socket.pobierz_wszystkie_adresy(nazwa) — rozwiązuje nazwę hosta na wszystkie jej adresy IPv4, zwrócone jako tablica napisów.

Socket.pobierz_nazwe_hosta(adres_ip) — odwrotny DNS — szuka nazwy hosta dla adresu IP. Zwraca sam napis IP, jeśli odwrotne rozwiązanie się nie powiedzie.

pokazl Socket.nazwa_hosta()                    # np. "moja-maszyna"
pokazl Socket.pobierz_adres_ip("localhost")    # "127.0.0.1"
pokazl Socket.pobierz_adres_ip("example.com")  # "93.184.215.14"

niech wszystkie = Socket.pobierz_wszystkie_adresy("dns.google")
pokazl wszystkie    # ["8.8.8.8", "8.8.4.4"]

Narzędzia portów

Socket.czy_port_wolny(port) / Socket.czy_port_wolny(port, adres) — sprawdza, czy port TCP jest wolny do przypisania. Domyślny adres to "127.0.0.1". Zwraca prawda, jeśli wolny, falsz, jeśli coś tam nasłuchuje.

Socket.wolny_port() — znajduje i zwraca nieużywany numer portu. Prosi kernel o przydzielenie (przypina się do portu 0, odczytuje, co zostało przydzielone, a potem natychmiast zwalnia). Przydatne w testach, gdzie chcesz znanego wolnego portu bez zaszywania numeru w kodzie.

pokazl Socket.czy_port_wolny(80)        # prawdopodobnie falsz na systemie z serwerem WWW
pokazl Socket.czy_port_wolny(54321)     # prawdopodobnie prawda

niech port = Socket.wolny_port()
niech srv = SerwerTcp.nowy(port)

Technika „znajdź wolny port” jest w teorii zagrożona wyścigiem (coś innego mogłoby go chwycić między wolny_port() a SerwerTcp.nowy(port)), ale w praktyce działa niezawodnie do celów testowych.

Http — klient HTTP

Biblioteka Http to wysokopoziomowy klient HTTP. Dajesz mu URL i czasownik, a on obsługuje połączenie, TLS dla HTTPS, kodowanie zapytania, przekierowania i parsowanie odpowiedzi do przyjaznego formatu obiektowego.

Http jest klasą statyczną. Wywołanie Http.nowy() rzuca Http jest klasą statyczną — użyj Http.get(), Http.post() itd..

import("http")

# Najprostsze zapytanie
niech odp = Http.get("https://example.com")
pokazl odp["status"]        # 200
pokazl odp["cialo"]          # HTML

Każda metoda zapytania zwraca obiekt odpowiedzi — obiekt AlexScript o identycznej strukturze, niezależnie od tego, której metody użyłeś. Pola są opisane szczegółowo w sekcji Obiekt odpowiedzi.

Kilka zachowań stosuje się do każdej metody:

Obiekt odpowiedzi

Każde zapytanie zwraca obiekt z tymi kluczami:

Klucz Typ Znaczenie
"status" całkowita kod statusu HTTP, np. 200, 404, 500
"wiadomosc" napis fraza powodu statusu, np. "OK", "Not Found"
"cialo" napis ciało odpowiedzi
"naglowki" obiekt nagłówki odpowiedzi jako obiekt (klucze małymi literami)
"czy_sukces" logiczna prawda, jeśli 200 ≤ status < 300
"czy_przekierowanie" logiczna prawda, jeśli 300 ≤ status < 400
"czy_blad_klienta" logiczna prawda, jeśli 400 ≤ status < 500
"czy_blad_serwera" logiczna prawda, jeśli 500 ≤ status < 600
niech odp = Http.get("https://api.example.com/users")

jesli odp["czy_sukces"] {
    niech dane = Json.parsuj(odp["cialo"])
    # ... przetwarzaj
} albojesli odp["czy_blad_klienta"] {
    pokazl "Błąd klienta: #{odp["status"]} #{odp["wiadomosc"]}"
} albojesli odp["czy_blad_serwera"] {
    pokazl "Błąd serwera: #{odp["status"]}"
}

Po podążeniu za przekierowaniami status odzwierciedla finalną odpowiedź — tę, której naprawdę chciałeś. Pośrednie skoki 301/302 nie są wystawiane.

Czasowniki

Każdy czasownik HTTP ma odpowiadającą mu metodę. Sygnatura argumentów różni się nieco: czasowniki w stylu GET przyjmują URL + nagłówki + opcje; czasowniki w stylu POST przyjmują URL + ciało + nagłówki + opcje.

Http.get(url) / Http.get(url, naglowki) / Http.get(url, naglowki, opcje) — wykonuje zapytanie GET.

niech odp = Http.get("https://api.example.com/users/42")
niech z_naglowkami = Http.get("https://api.example.com/protected",
    {"Authorization": "Bearer abc123"})

Http.post(url, cialo) / Http.post(url, cialo, naglowki) / Http.post(url, cialo, naglowki, opcje) — wykonuje zapytanie POST z podanym ciałem. Ciało jest wysyłane bez zmian — żadnego automatycznego kodowania.

Http.post("https://api.example.com/log", "ERROR: connection lost")

Http.put(url, cialo) / Http.put(url, cialo, naglowki) / Http.put(url, cialo, naglowki, opcje) — taka sama sygnatura jak post, ale PUT.

Http.patch(url, cialo) / Http.patch(url, cialo, naglowki) / Http.patch(url, cialo, naglowki, opcje) — taka sama sygnatura, ale PATCH.

Http.delete(url) / Http.delete(url, naglowki) / Http.delete(url, naglowki, opcje) — wykonuje zapytanie DELETE. Bez ciała.

Http.head(url) / Http.head(url, naglowki) / Http.head(url, naglowki, opcje) — zapytanie HEAD. cialo w obiekcie odpowiedzi zawsze będzie puste (odpowiedzi HEAD z definicji nie mają ciała); nagłówki i status nadal są wypełnione.

Http.options(url) / Http.options(url, naglowki) — zapytanie OPTIONS, używane do preflightu CORS i odkrywania możliwości.

niech ohead = Http.head("https://example.com/duzy_plik.zip")
pokazl ohead["naglowki"]["content-length"]    # sprawdź rozmiar przed pobieraniem

Wygodne pomocniki JSON

Większość nowoczesnych API wysyła i odbiera JSON, więc dedykowane pomocniki obsługują kodowanie/dekodowanie automatycznie.

Http.get_json(url) / Http.get_json(url, naglowki) / Http.get_json(url, naglowki, opcje) — wykonuje GET, parsuje ciało odpowiedzi jako JSON i zwraca sparsowaną wartość bezpośrednio (nie pełny obiekt odpowiedzi). Wysyła Accept: application/json. Rzuca błąd, jeśli odpowiedź nie jest poprawnym JSON-em.

niech uzytkownik = Http.get_json("https://api.example.com/users/42")
pokazl uzytkownik["imie"]
pokazl uzytkownik["email"]

Http.post_json(url, dane) / Http.post_json(url, dane, naglowki) / Http.post_json(url, dane, naglowki, opcje) — serializuje dane do JSON-a, wysyła POST z Content-Type: application/json i Accept: application/json, a następnie parsuje odpowiedź jako JSON.

niech wynik = Http.post_json("https://api.example.com/users",
    {"imie": "Anna", "email": "[email protected]"})

pokazl wynik["id"]    # ID przypisane przez serwer, sparsowane z odpowiedzi JSON

Jeśli dane jest już napisem, jest wysyłane bez zmian; w przeciwnym razie konwertowane przez Json.generuj. Argumentem może być dowolna wartość AlexScript serializowalna do JSON-a.

Http.put_json(url, dane) / Http.put_json(url, dane, naglowki) / Http.put_json(url, dane, naglowki, opcje) — to samo co post_json, ale PUT. Jeśli odpowiedź nie jest JSON-em, zwraca ciało jako zwykły napis zamiast rzucać.

POST z formularzem

Http.post_formularz(url, dane) / Http.post_formularz(url, dane, naglowki) / Http.post_formularz(url, dane, naglowki, opcje) — POST z ciałem zakodowanym jako formularz (application/x-www-form-urlencoded). Przydatne dla starszych API i tradycyjnych formularzy HTML.

niech odp = Http.post_formularz(
    "https://api.example.com/login",
    {"user": "anna", "pass": "tajne"}
)

Klucze i wartości są kodowane URL-em automatycznie. Zwraca pełny obiekt odpowiedzi (nie sparsowany JSON).

Pobieranie pliku

Http.pobierz(url, sciezka) — pobiera ciało zapytania GET bezpośrednio do pliku, strumieniując kawałkami zamiast wczytywać wszystko do pamięci. Przydatne dla dużych plików.

Http.pobierz("https://example.com/duzy_plik.zip", "./pobrane.zip")

Zwraca prawda przy powodzeniu. Nie podąża automatycznie za przekierowaniami — wskaż go na stabilny URL.

Narzędzia URL

Garść statycznych metod do obsługi URL-i. Te nie wykonują żadnych wywołań sieciowych — to czyste operacje na napisach.

Http.koduj_url(tekst) — koduje napis procentowo, żeby był bezpieczny do osadzenia w URL-u. Spacje stają się %20 (nie +), znaki specjalne zostają zakodowane itd. Kodowanie idzie według reguł application/x-www-form-urlencoded.

Http.dekoduj_url(tekst) — odwrotność koduj_url.

pokazl Http.koduj_url("witaj świecie!")          # "witaj%20%C5%9Bwiecie%21"
pokazl Http.dekoduj_url("witaj%20%C5%9Bwiecie")   # "witaj świecie"

Http.parsuj_url(url) — parsuje URL na komponenty. Zwraca obiekt z ośmioma kluczami:

Klucz Znaczenie
"schemat" schemat (np. "https")
"host" nazwa hosta
"port" numer portu jako liczba całkowita (0, jeśli nieokreślony)
"sciezka" ścieżka
"zapytanie" query string (bez wiodącego ?)
"fragment" fragment (bez wiodącego #)
"uzytkownik" nazwa użytkownika z URL-a user:pass@host
"haslo" hasło z URL-a user:pass@host

Brakujące komponenty wracają jako puste napisy (lub 0 dla portu).

niech p = Http.parsuj_url("https://example.com:8080/api/v1?k=v#sec")
pokazl p["schemat"]      # "https"
pokazl p["host"]         # "example.com"
pokazl p["port"]         # 8080
pokazl p["sciezka"]      # "/api/v1"
pokazl p["zapytanie"]    # "k=v"
pokazl p["fragment"]     # "sec"

Http.zbuduj_url(schemat, host) / Http.zbuduj_url(schemat, host, port) / Http.zbuduj_url(schemat, host, port, sciezka) / Http.zbuduj_url(schemat, host, port, sciezka, zapytanie) — składa URL z komponentów. Pozostałe argumenty mają sensowne wartości domyślne (brak sekcji portu, ścieżka /, brak zapytania).

pokazl Http.zbuduj_url("https", "example.com", 443, "/api", "q=1")
# "https://example.com:443/api?q=1"

Http.zbuduj_zapytanie(params) — buduje query string zakodowany URL-em z obiektu. Klucze i wartości są kodowane URL-em automatycznie.

pokazl Http.zbuduj_zapytanie({"a": "1", "b": "spacja w wartosci"})
# "a=1&b=spacja+w+wartosci"

Http.parsuj_zapytanie(tekst) — odwrotność zbuduj_zapytanie. Parsuje query string do obiektu.

niech sp = Http.parsuj_zapytanie("x=10&y=20")
pokazl sp["x"]    # "10"
pokazl sp["y"]    # "20"

Narzędzia te przydają się i po stronie serwera (do obsługi przychodzących zapytań), i po stronie klienta (do budowania URL-i).

Opcje

Opcjonalny argument opcje w większości metod to obiekt z tymi kluczami:

"timeout" — limit czasu łączenia/czytania/pisania w sekundach. Domyślnie 30.

"przekierowania" — maksymalna liczba przekierowań do podążania. Domyślnie 5. Ustaw na 0, żeby wyłączyć podążanie za przekierowaniami.

niech odp = Http.get("https://example.com",
    {},                                            # bez dodatkowych nagłówków
    {"timeout": 5, "przekierowania": 0})           # 5s limit, bez przekierowań