Rozbudowana biblioteka diagnostyczna dla H# i ekosystemu bytes, inspirowana miette (Rust) — ale z większą liczbą stylów renderowania, motywami kolorystycznymi, lokalizacją komunikatów i łańcuchami przyczyn.
100% H#. Zero extern. Zero FFI. Tylko standardowa biblioteka H# (strings, fmt, term, env, test).
× error[nidus::eval::div_zero]: dzielenie przez zero
╭─[main.ns:1:9]
1 │ let y = x / 0
· ─┬─
· ╰── tu nie można dzielić przez zero
╰────
help: sprawdź mianownik przed wykonaniem dzielenia
see: https://example.com/errors/div-zero
miette w Rust ma jeden domyślny "graficzny" handler i drugi, "narratable", do czytników ekranu. nidus idzie dalej i oferuje siedem gotowych stylów renderowania tej samej diagnostyki, pełną paletę motywów (w tym truecolor), oraz w pełni lokalizowalne słownictwo (błąd/pomoc/uwaga zamiast tylko error/help/note) — wszystko to bez ani jednej linijki kodu spoza H#.
Przez bytes (menedżer pakietów H#):
bytes add nidusRęcznie, jako zależność Git w Bytes.hk Twojego projektu:
[deps]
-> nidus => git
Następnie w kodzie:
use "github.com/twoje-konto/nidus" from "nidus"
Uwaga dot. importu: skoro
nidusjest importowany pod aliasem, typy biblioteki (np.Diagnostic,Theme,Config,Style,Severity,Label,Span,Words,Charset,Palette) również wymagają prefiksu, np.nidus::Diagnostic,nidus::Style::Graphical— dokładnie tak samo jak dla zwykłych funkcji (config::Projectwbytes). Wewnątrz samej biblioteki (pliksrc/lib.h#) wszystko jest oczywiście bez prefiksu.
use "github.com/twoje-konto/nidus" from "nidus"
fn main() is
let src: string = "let y = x / 0\nwrite(y)\n"
let mut d: nidus::Diagnostic = nidus::error("dzielenie przez zero")
d = d.with_code("nidus::eval::div_zero")
d = d.with_source("main.ns", src)
d = d.with_label(nidus::primary_label(8, 5, "tu nie można dzielić przez zero"))
d = d.with_help("sprawdź mianownik przed wykonaniem dzielenia")
nidus::report(d) ;; sam dobierze styl i motyw do terminala
end
Więcej gotowych przykładów w examples/:
| Plik | Co pokazuje |
|---|---|
basic.h# |
Minimalna diagnostyka z jedną etykietą |
multi_label.h# |
Dwie etykiety (główna + pomocnicza) w jednym fragmencie |
custom_theme.h# |
Motyw retro (truecolor), polska lokalizacja, własny motyw |
all_styles.h# |
Ta sama diagnostyka we wszystkich 7 stylach |
related_chain.h# |
Łańcuch przyczyn (with_related) |
Rdzeń biblioteki. Budowany fluent-builderem (każda metoda with_* zwraca
zmodyfikowaną wartość, więc łańcuchujesz przypisania):
let mut d: nidus::Diagnostic = nidus::error("wiadomość")
d = d.with_code("moj::kod::bledu") ;; identyfikator, np. do wyszukiwania w dokumentacji
d = d.with_help("sugestia naprawy")
d = d.with_note("dodatkowy kontekst")
d = d.with_url("https://...")
d = d.with_source("plik.ns", tresc_zrodla)
d = d.with_label(nidus::primary_label(offset, dlugosc, "opis"))
d = d.with_related(inna_diagnostyka) ;; łańcuch przyczyn
Konstruktory: nidus::error(msg), nidus::warning(msg), nidus::advice(msg),
nidus::diagnostic(msg) (alias error). Modyfikatory poziomu:
.as_error(), .as_warning(), .as_advice().
Dlaczego reassignment (
d = d.with_x(...)), a nie jeden długi łańcucherror(...).with_x(...).with_y(...)rozbity na wiele linii? Bo H# — na podstawie oficjalnej gramatyki i przykładów wstd/— nie gwarantuje kontynuacji wyrażenia zaczynającej się od.na nowej linii. Łańcuch na jednej linii (a.b().c()) jest w pełni bezpieczny (patrziterw README H#), więc krótkie łańcuchy możesz pisać w jednej linii —nidussam z tego korzysta wewnętrznie.
Span { offset, len } — zakres bajtowy w kodzie źródłowym.
Label { sp, text, primary } — pojedyncze podświetlenie.
nidus::span(offset, len) ;; Span
nidus::point(offset) ;; Span o długości 1
nidus::label(offset, len, "opis") ;; etykieta pomocnicza
nidus::label_at(offset, "opis") ;; jw., długość 1
nidus::primary_label(offset, len, "opis") ;; etykieta główna (kolor błędu)
nidus::primary_label_at(offset, "opis")
Diagnostyka może mieć dowolną liczbę etykiet, na tej samej lub różnych
liniach — styl Graphical/Ascii narysuje je wszystkie, w tym efekt
"schodkowy" gdy kilka etykiet wskazuje tę samą linię (jak w miette).
Uwaga o kolumnach: tak jak reszta stringów w H# (
s[i..i+1]),nidusliczy pozycje w bajtach. Dla źródeł czysto ASCII kolumny są dokładne. Wielobajtowe znaki UTF-8 w analizowanym kodzie źródłowym mogą przesunąć wizualną kolumnę — to ograniczenie modelu stringów H#, nie samej biblioteki.
nidus renderuje tę samą Diagnostic na siedem sposobów:
| Styl | Kiedy używać |
|---|---|
Style::Graphical |
Terminal interaktywny, UTF-8 — pełny widok z fragmentem kodu, strzałkami, kolorami |
Style::Ascii |
Jak wyżej, ale bez znaków unikodowych (stare terminale, TERM=dumb z wymuszonym kolorem) |
Style::Narratable |
Czytniki ekranu — pełne zdania zamiast grafiki ASCII |
Style::Minimal |
Jedna linia na wpis, plik:linia:kolumna: poziom: wiadomość — do grep/logów |
Style::Json |
Wyjście maszynowe do dalszego przetwarzania (pola zawsze po angielsku) |
Style::Markdown |
Blok gotowy do wklejenia w komentarz Pull Requesta |
Style::GithubActions |
Adnotacje ::error file=...:: czytane przez GitHub Actions |
Wybór stylu i motywu:
let cfg: nidus::Config = nidus::config_default() ;; autodetekcja (patrz niżej)
let cfg2: nidus::Config = nidus::config_with(nidus::Style::Json, nidus::theme_mono())
nidus::report_with(d, cfg2)
nidus::auto_style():
GITHUB_ACTIONS=true→GithubActions- zmienna
NIDUS_STYLE(graphical/ascii/narratable/minimal/json/markdown/github) → wymuszony styl - stdout nie jest terminalem (
term::is_tty()== false) →Minimal - terminal z UTF-8 (
LANG/LC_ALL) →Graphical - w przeciwnym razie →
Ascii
nidus::auto_theme(): honoruje NO_COLOR (zero kolorów) i TERM=dumb,
oraz wspiera unikod tylko gdy terminal go deklaruje.
Gotowe motywy (Theme = Charset + Palette + Words + ustawienia layoutu):
| Funkcja | Opis |
|---|---|
theme_dark() |
Domyślny — unikod + jasne kolory na ciemnym tle |
theme_light() |
Unikod + stonowane kolory na jasnym tle |
theme_ascii() |
ASCII + kolory |
theme_mono() |
ASCII, bez kolorów — najbezpieczniejszy wszędzie |
theme_mono_unicode() |
Unikod, bez kolorów |
theme_retro() |
Truecolor (\x1b[38;2;r;g;b) — neonowe różowo-fioletowe akcenty |
theme_polski() |
theme_dark() z polskimi etykietami (błąd/pomoc/uwaga/...) |
Własny motyw ze składników:
let mut t: nidus::Theme = nidus::theme_dark()
t = t.with_context(3) ;; 3 linie kontekstu zamiast domyślnej 1
t = t.with_wrap(60) ;; węższe zawijanie help/note
t = t.with_words(nidus::words_pl()) ;; lub własny nidus::Words { ... }
t = t.with_palette(moja_paleta) ;; własna struktura Palette
t = t.with_charset(moj_zestaw_znakow)
Ponieważ Palette to zwykłe pola typu string z kodami ANSI, możesz
zbudować dowolny kolor (w tym truecolor) — zobacz theme_retro() w
src/lib.h# jako wzór.
let cause: nidus::Diagnostic = nidus::error("plik nie istnieje").with_code("nidus::io::not_found")
let mut top: nidus::Diagnostic = nidus::error("nie udało się wczytać konfiguracji")
top = top.with_related(cause)
nidus::report(top)
Każdy styl renderuje related rekurencyjnie (z wcięciem), analogicznie do
#[source]/łańcucha przyczyn w miette.
let ds: [nidus::Diagnostic] = [blad1, blad2, ostrzezenie1]
nidus::print_all(ds, nidus::config_default())
;; wypisuje każdą diagnostykę, a na końcu np. "2 błędy, 1 ostrzeżenie"
nidus::summarize(ds) samodzielnie zwraca tylko tekst podsumowania
(z poprawną polską odmianą liczby mnogiej: 1 błąd / 2-4 błędy / 5+ błędów).
nidus::die(d) ;; report(d) + exit(1 dla Error, 0 dla Warning/Advice)
nidus::die_with(d, cfg) ;; jw., z jawną konfiguracją
Lekki odpowiednik Result<T, Diagnostic>, do użycia we własnych funkcjach
(biblioteka niczego nie narzuca — równie dobrze możesz zwracać T? i
korzystać z operatora ?):
enum Outcome<T> is
Success(T)
Failure(Diagnostic)
end
fn wczytaj_port(s: string) -> nidus::Outcome<int> is
if !s.is_numeric() is
return nidus::Outcome::Failure(nidus::error("`" + s + "` nie jest liczbą").with_code("nidus::config::bad_port"))
end
return nidus::Outcome::Success(conv::str_to_int(s))
end
Testy jednostkowe są dołączone bezpośrednio w src/lib.h# (zgodnie z tym,
jak bytes test wyszukuje #[test] — również wewnątrz src/):
bytes test- Pozycje (
offset) są liczone w bajtach, nie w punktach kodowych Unicode — zgodnie z modelem stringów H# (s[i..i+1]). Dla źródeł ASCII jest to dokładne. - Efekt "schodkowy" dla wielu etykiet w tej samej linii jest uproszczony
względem
miette(brak pełnego algorytmu unikania kolizji przy bardzo gęsto upakowanych etykietach) — w praktyce dla 2-4 etykiet na linię wygląda identycznie. - Kod nie był uruchamiany przez
h# check/bytes buildw tym środowisku (brak lokalnego toolchaina LLVM 21 + Rust). Składnia została ręcznie zweryfikowana zdanie po zdaniu względem README H# v0.9 oraz wzorców zstd/*.h#iexamples/showcase.h#z repozytorium H#-Sharp. Jeśli natrafisz na błąd kompilacji, to najpewniej pojedyncza literówka składniowa — logika i architektura są kompletne.
MIT.