commit e0e4a0d6c702ed4369a339bb4ce36291c79ad7e5 Author: Semih Date: Fri Oct 2 22:31:40 2026 +0300 KabaLang örnekleri: konsol, masaüstü ve web için dokuz proje Konsol: fraktal (paralel şeritli 24-bit Mandelbrot/Julia + yakınlaşma), sudoku (geri izlemeli, canlı animasyon), fatura (JSON → terminal tablosu, PDF ve Excel). Masaüstü: hesap makinesi (işlem önceliği, canlı bağlama), odak (Pomodoro), parola atölyesi (entropi ölçer, SHA-256). Web: pano (Kanban REST API + SQLite), kisalt (302 yönlendirme, SVG istatistik), anket (adres başına tek oy, sunucuda çizilen grafik). Hepsi `kabalang calistir` ile derlenip çalışır; web uygulamaları yalnız 127.0.0.1'i dinler, SQL parametre bağlamalı, HTML çıktısı kaçırılır. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ce9f052 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Dil istatistiği: .kbl dosyaları KabaLang kaynak kodudur +*.kbl linguist-language=KabaLang linguist-detectable=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbeff78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Derleme çıktıları — `kabalang calistir` ikiliyi kaynağın yanına bırakır +konsol/fraktal/fraktal +konsol/sudoku/sudoku +konsol/fatura/fatura +arayuz/hesap-makinesi/hesap-makinesi +arayuz/odak/odak +arayuz/parola-atolyesi/parola-atolyesi +web/pano/pano +web/kisalt/kisalt +web/anket/anket +*.apparmor +*.cpp +*.o + +# Çalışma anında üretilen veri +*.db +fatura.pdf +fatura.xlsx diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef9239d --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# KabaLang Örnekleri + +Türkçe sözdizimli, derlenen programlama dili **KabaLang** ile yazılmış dokuz +çalışan proje: terminalde, masaüstünde ve tarayıcıda. Her biri tek dosyadır ve +tek komutla derlenip çalışır. + +```bash +kabalang calistir konsol/fraktal/fraktal.kbl +``` + +> KabaLang **önizleme 0.5** sürümündedir. Kılavuz, standart kütüphane ve kurulum: +> [dev.kabaforce.com/kabalang](https://dev.kabaforce.com/kabalang/) + +![KabaLang Kcode içinde](medya/kcode.png) + +--- + +## Konsol + +| | Proje | Ne gösteriyor | +|---|---|---| +| ![](medya/fraktal.png) | [**Fraktal**](konsol/fraktal) | 24-bit renkli Mandelbrot ve Julia; `birlikte:` + `baslat` ile dört şerit **paralel** hesaplanır, `--yakinlas` ile 120 karelik dalış | +| ![](medya/sudoku.png) | [**Sudoku**](konsol/sudoku) | Geri izlemeli çözücü, adım adım canlı animasyon; `sinif` ve `sayilabilir` hücre durumu | +| ![](medya/fatura.png) | [**Fatura**](konsol/fatura) | JSON siparişten KDV'li fatura: kutu çizgili terminal tablosu, ardından **PDF ve Excel** çıktısı | + +## Masaüstü + +| | Proje | Ne gösteriyor | +|---|---|---| +| ![](medya/hesap-makinesi.png) | [**Hesap makinesi**](arayuz/hesap-makinesi) | İşlem önceliğini bilen hesap motoru; ekran **canlı bağlama** ile kendiliğinden güncellenir | +| ![](medya/odak.png) | [**Odak**](arayuz/odak) | Pomodoro zamanlayıcı: `zamanlayici` bloğu, ilerleme çubuğu, oturum kaydı, masaüstü bildirimi | +| ![](medya/parola-atolyesi.png) | [**Parola atölyesi**](arayuz/parola-atolyesi) | Parola üretici, entropi ölçer, SHA-256 özeti; sürgü ve girdi etiketlere canlı akar | + +## Web + +| | Proje | Ne gösteriyor | +|---|---|---| +| ![](medya/pano.png) | [**Pano**](web/pano) | Kanban panosu: JSON REST API (GET · POST · PUT · DELETE) + SQLite + tek sayfalık ön yüz | +| ![](medya/kisalt-istatistik.png) | [**Kısalt**](web/kisalt) | Bağlantı kısaltıcı: gerçek 302 yönlendirme, tıklama sayacı, sunucuda çizilen SVG grafik | +| ![](medya/anket.png) | [**Anket**](web/anket) | Canlı anket: adres başına tek oy, POST → 303 → GET, sonuç grafiği KabaLang'da üretilir | + +Web uygulamaları yalnız `127.0.0.1`'i dinler. Kullanıcıdan gelen her değer +SQL'e **parametre olarak** bağlanır, HTML'e girerken `web.kacir` ile kaçırılır. + +--- + +## Çalıştırmak + +```bash +# Konsol +kabalang calistir konsol/fraktal/fraktal.kbl -- --yakinlas +kabalang calistir konsol/sudoku/sudoku.kbl +cd konsol/fatura && kabalang calistir fatura.kbl + +# Masaüstü +kabalang calistir arayuz/hesap-makinesi/hesap-makinesi.kbl +kabalang calistir arayuz/odak/odak.kbl +kabalang calistir arayuz/parola-atolyesi/parola-atolyesi.kbl + +# Web (her biri kendi klasöründen; tarayıcıda açın) +cd web/pano && kabalang calistir pano.kbl # http://127.0.0.1:8471 +cd web/kisalt && kabalang calistir kisalt.kbl # http://127.0.0.1:8472 +cd web/anket && kabalang calistir anket.kbl # http://127.0.0.1:8473 +``` + +Yalnız derlemek için: `kabalang derle -o `. +Her projenin klasöründe ayrıntılı bir README vardır. diff --git a/arayuz/hesap-makinesi/README.md b/arayuz/hesap-makinesi/README.md new file mode 100644 index 0000000..2c6c7a4 --- /dev/null +++ b/arayuz/hesap-makinesi/README.md @@ -0,0 +1,25 @@ +# Hesap Makinesi + +İşlem önceliğini bilen, geçmişini tutan masaüstü hesap makinesi. +Hesaplama motoru dahil her satırı KabaLang. + +![Hesap Makinesi](../../medya/hesap-makinesi.png) + +```bash +kabalang calistir hesap-makinesi.kbl +``` + +## Neyi gösteriyor + +| Özellik | Nerede | +|---|---| +| **İki yığınlı ifade değerlendirici**: `2+3×4` → `14` | `hesapla()` | +| `sayilabilir Islem` ile kapalı işlem kümesi | dosyanın başı | +| **Canlı bağlama**: `Ekran.metin = canli_sonuc(Ifade.metin)` | `etiket Ekran` | +| Hata yakalama: `1÷0` çökmez, ekranda `Tanımsız` yazar | `canli_sonuc()` | +| Türkçe sayı biçimi: `1.234.567,5` | `bicimle()`, `binlik()` | +| Geçmişten satır seçince sonucu yeniden kullanma | `liste Gecmis` → `secildiginde` | +| `izinler:` ile ağa çıkmadığını beyan etme (derleme anında denetlenir) | dosyanın başı | + +Ekran hiçbir yerde elle güncellenmiyor. Tuşlar yalnız ifadeyi değiştiriyor, +ekran bağlı olduğu için kendini yeniliyor. diff --git a/arayuz/hesap-makinesi/hesap-makinesi.kbl b/arayuz/hesap-makinesi/hesap-makinesi.kbl new file mode 100644 index 0000000..dd315cd --- /dev/null +++ b/arayuz/hesap-makinesi/hesap-makinesi.kbl @@ -0,0 +1,337 @@ +# ╔══════════════════════════════════════════════════════════════╗ +# ║ KabaLang Hesap Makinesi ║ +# ║ İşlem önceliğini bilen, geçmişini tutan bir masaüstü ║ +# ║ hesap makinesi. Hesaplama motoru baştan sona KabaLang. ║ +# ╚══════════════════════════════════════════════════════════════╝ +# +# Çalıştır: kabalang calistir hesap-makinesi.kbl + +kullan kaba.arayuz +kullan kaba.sistem + +izinler: + ag_erisim = yanlis + +# ── 1. HESAP MOTORU ─────────────────────────────────────────────── +# İşlem türleri kapalı bir küme: derleyici yanlış üyeyi yakalar. +sayilabilir Islem: + TOPLA + CIKAR + CARP + BOL + +fonksiyon oncelik(i: Islem) -> sayi: + eger i == Islem.CARP veya i == Islem.BOL: + don 2 + don 1 + +fonksiyon uygula(a: ondalik, b: ondalik, i: Islem) -> ondalik: + eger i == Islem.TOPLA: + don a + b + degilse eger i == Islem.CIKAR: + don a - b + degilse eger i == Islem.CARP: + don a * b + don a / b + +fonksiyon islem_mi(c: metin) -> mantik: + don "+−×÷".icerir(c) + +fonksiyon islem_coz(c: metin) -> Islem: + eger c == "+": + don Islem.TOPLA + degilse eger c == "−": + don Islem.CIKAR + degilse eger c == "×": + don Islem.CARP + don Islem.BOL + +# İki yığınlı değerlendirici: sayılar bir yığına, işlemler öbürüne. +# Yeni gelen işlemin önceliği düşükse, yığındaki güçlü işlemler önce yapılır. +fonksiyon hesapla(ifade: metin) -> ondalik: + sayilar = [0.0] + sayilar.sil(0) + islemler = [Islem.TOPLA] + islemler.sil(0) + i = 0 + iken i < ifade.uzunluk(): + c = ifade.parca(i, 1) + eksi_basta = c == "−" ve (i == 0 veya islem_mi(ifade.parca(i - 1, 1))) + eger islem_mi(c) ve degil eksi_basta: + yeni = islem_coz(c) + iken islemler.uzunluk() > 0 ve oncelik(islemler[islemler.uzunluk() - 1]) >= oncelik(yeni): + b = sayilar[sayilar.uzunluk() - 1] + sayilar.sil(sayilar.uzunluk() - 1) + a = sayilar[sayilar.uzunluk() - 1] + sayilar.sil(sayilar.uzunluk() - 1) + sayilar.ekle(uygula(a, b, islemler[islemler.uzunluk() - 1])) + islemler.sil(islemler.uzunluk() - 1) + islemler.ekle(yeni) + i = i + 1 + devam + bas = i + i = i + 1 + iken i < ifade.uzunluk() ve degil islem_mi(ifade.parca(i, 1)): + i = i + 1 + sayi_metni = ifade.parca(bas, i - bas).degistir("−", "-").degistir(",", ".") + sayilar.ekle(ondalik(sayi_metni)) + iken islemler.uzunluk() > 0: + b = sayilar[sayilar.uzunluk() - 1] + sayilar.sil(sayilar.uzunluk() - 1) + a = sayilar[sayilar.uzunluk() - 1] + sayilar.sil(sayilar.uzunluk() - 1) + sayilar.ekle(uygula(a, b, islemler[islemler.uzunluk() - 1])) + islemler.sil(islemler.uzunluk() - 1) + don sayilar[0] + +# ── 2. SAYIYI GÜZEL YAZMAK ──────────────────────────────────────── +# 1234567.5 → "1.234.567,5" (Türkçe binlik ve ondalık ayırıcı) +fonksiyon binlik(tam: metin) -> metin: + eksi = tam.ile_baslar("-") + eger eksi: + tam = tam.parca(1, tam.uzunluk() - 1) + sonuc = "" + sayac = 0 + i = tam.uzunluk() - 1 + iken i >= 0: + eger sayac > 0 ve sayac % 3 == 0: + sonuc = "." + sonuc + sonuc = tam.parca(i, 1) + sonuc + sayac = sayac + 1 + i = i - 1 + eger eksi: + don "−" + sonuc + don sonuc + +fonksiyon bicimle(x: ondalik) -> metin: + ham = f"{x:.8f}" + nokta = ham.bul(".") + tam = ham.parca(0, nokta) + kesir = ham.parca(nokta + 1, ham.uzunluk() - nokta - 1) + iken kesir.ile_biter("0"): + kesir = kesir.parca(0, kesir.uzunluk() - 1) + eger tam == "-0" ve kesir == "": + tam = "0" + eger kesir == "": + don binlik(tam) + don binlik(tam) + "," + kesir + +# Ekrandaki "1.234,5" biçimini motorun okuduğu "1234,5" biçimine çevirir. +fonksiyon ham_sayi(gorunen: metin) -> metin: + don gorunen.degistir(".", "") + +# ── 3. TUŞ MANTIĞI ──────────────────────────────────────────────── +fonksiyon son_karakter(m: metin) -> metin: + eger m.uzunluk() == 0: + don "" + don m.parca(m.uzunluk() - 1, 1) + +# Arka arkaya iki işlem tuşu: sonuncusu öncekinin yerine geçer. +fonksiyon ifadeye_ekle(ifade: metin, tus: metin) -> metin: + eger islem_mi(tus): + eger ifade == "": + eger tus == "−": + don "−" + don "" + eger islem_mi(son_karakter(ifade)): + don ifade.parca(0, ifade.uzunluk() - 1) + tus + eger tus == ",": + # Aynı sayıda ikinci virgül olmaz + i = ifade.uzunluk() - 1 + iken i >= 0 ve degil islem_mi(ifade.parca(i, 1)): + eger ifade.parca(i, 1) == ",": + don ifade + i = i - 1 + eger ifade == "" veya islem_mi(son_karakter(ifade)): + don ifade + "0," + don ifade + tus + +# Sondaki yarım işlemi at: "12+" → "12" +fonksiyon temiz_ifade(ifade: metin) -> metin: + iken ifade != "" ve islem_mi(son_karakter(ifade)): + ifade = ifade.parca(0, ifade.uzunluk() - 1) + don ifade + +# Ekranın göstereceği metin. Sıfıra bölme gibi hatalar burada yakalanır. +fonksiyon canli_sonuc(ifade: metin) -> metin: + ifade = temiz_ifade(ifade) + eger ifade == "" veya ifade == "−": + don "0" + dene: + don bicimle(hesapla(ifade)) + yakala h: + don "Tanımsız" + +fonksiyon yuzde(ifade: metin) -> metin: + sonuc = canli_sonuc(ifade) + eger sonuc == "Tanımsız": + don ifade + don ham_sayi(bicimle(hesapla(ham_sayi(sonuc)) / 100.0)) + +# ± tuşu: o anki sonucun işaretini çevirir +fonksiyon isaret(ifade: metin) -> metin: + eger canli_sonuc(ifade) == "Tanımsız" veya temiz_ifade(ifade) == "": + don ifade + don ham_sayi(bicimle(0.0 - hesapla(temiz_ifade(ifade)))) + +ilk_acilis = dogru + +pencere HesapMakinesi: + baslik = "KabaLang Hesap Makinesi" + genislik = 840 + yukseklik = 660 + tema = "koyu" + + yatay_kutu Govde: + dikey_kutu SolTaraf: + etiket Ifade: + metin = "" + stil = "font-size: 20px; color: #7d8aa8; padding: 8px 6px 0px 6px; min-height: 28px;" + + # Ekran BAĞLIDIR: ifade her değiştiğinde sonuç kendiliğinden yenilenir. + etiket Ekran: + metin = canli_sonuc(Ifade.metin) + stil = "font-size: 60px; font-weight: 800; color: #ffffff; padding: 0px 6px 14px 6px;" + + yatay_kutu Satir1: + dugme Temizle: + metin = "C" + stil = "font-size: 20px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1a2030; color: #22d3ee;" + tiklandiginda: + Ifade.metin = "" + dugme Sil: + metin = "⌫" + stil = "font-size: 20px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1a2030; color: #22d3ee;" + tiklandiginda: + eger Ifade.metin.uzunluk() > 0: + Ifade.metin = Ifade.metin.parca(0, Ifade.metin.uzunluk() - 1) + dugme Yuzde: + metin = "%" + stil = "font-size: 20px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1a2030; color: #22d3ee;" + tiklandiginda: + Ifade.metin = yuzde(Ifade.metin) + dugme Bol: + metin = "÷" + stil = "font-size: 26px; font-weight: 700; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1d2a4d; color: #7fb0ff;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "÷") + + yatay_kutu Satir2: + dugme Yedi: + metin = "7" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "7") + dugme Sekiz: + metin = "8" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "8") + dugme Dokuz: + metin = "9" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "9") + dugme Carp: + metin = "×" + stil = "font-size: 26px; font-weight: 700; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1d2a4d; color: #7fb0ff;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "×") + + yatay_kutu Satir3: + dugme Dort: + metin = "4" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "4") + dugme Bes: + metin = "5" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "5") + dugme Alti: + metin = "6" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "6") + dugme Cikar: + metin = "−" + stil = "font-size: 26px; font-weight: 700; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1d2a4d; color: #7fb0ff;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "−") + + yatay_kutu Satir4: + dugme Bir: + metin = "1" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "1") + dugme Iki: + metin = "2" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "2") + dugme Uc: + metin = "3" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "3") + dugme Topla: + metin = "+" + stil = "font-size: 26px; font-weight: 700; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #1d2a4d; color: #7fb0ff;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "+") + + yatay_kutu Satir5: + dugme Isaret: + metin = "±" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = isaret(Ifade.metin) + dugme Sifir: + metin = "0" + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, "0") + dugme Virgul: + metin = "," + stil = "font-size: 24px; font-weight: 600; min-height: 68px; min-width: 92px; border-radius: 16px; border: 1px solid #1c2336; background: #121829; color: #e6e9f2;" + tiklandiginda: + Ifade.metin = ifadeye_ekle(Ifade.metin, ",") + dugme Esittir: + metin = "=" + stil = "font-size: 26px; font-weight: 800; min-height: 68px; min-width: 92px; border-radius: 16px; border: none; background: #377ef4; color: #ffffff;" + tiklandiginda: + sonuc = canli_sonuc(Ifade.metin) + eger sonuc != "Tanımsız" ve Ifade.metin != ham_sayi(sonuc): + Gecmis.ekle(temiz_ifade(Ifade.metin) + " = " + sonuc) + Ifade.metin = ham_sayi(sonuc) + + + dikey_kutu SagTaraf: + etiket GecmisBasligi: + metin = "Geçmiş" + stil = "font-size: 17px; font-weight: 700; color: #22d3ee; padding: 6px 4px;" + + liste Gecmis: + stil = "font-size: 18px; color: #c9d1e6; background: #0d1220; border: 1px solid #1c2336; border-radius: 12px; padding: 8px; min-width: 330px; max-width: 360px;" + secildiginde: + # Geçmişten bir satır seçince sonucu yeniden kullan + satir = Gecmis.secili_metin + eger satir.icerir(" = "): + parcalar = satir.bol(" = ") + Ifade.metin = ham_sayi(parcalar[1]) + + dugme GecmisiTemizle: + metin = "Geçmişi temizle" + stil = "font-size: 14px; min-height: 36px; border-radius: 10px; border: 1px solid #1c2336; background: #0d1220; color: #7d8aa8;" + tiklandiginda: + Gecmis.temizle() + + # Açılışta geçmişe, işlem önceliğini gösteren örnekler düşer. + zamanlayici 50ms: + eger ilk_acilis: + ilk_acilis = yanlis + dongu ornek icinde ["2+3×4", "100−20÷4×3", "−15+40×2", "99,9×3", "22÷7", "1250×12−2500"]: + Gecmis.ekle(ornek + " = " + bicimle(hesapla(ornek))) + Ifade.metin = "1250×12−2500" diff --git a/arayuz/odak/README.md b/arayuz/odak/README.md new file mode 100644 index 0000000..c257a34 --- /dev/null +++ b/arayuz/odak/README.md @@ -0,0 +1,26 @@ +# Odak — Pomodoro zamanlayıcı + +Odaklan, mola ver, günün oturumlarını kaydet. Oturum bitince masaüstü +bildirimi gelir. + +![Odak](../../medya/odak.png) + +```bash +kabalang calistir odak.kbl +ODAK_DENEME=1 kabalang calistir odak.kbl # deneme kipi: 1 dakika = 1 saniye +``` + +Görüntüdeki iki kayıt (25 dk odak + 5 dk mola) ve yarıya gelmiş oturum +deneme kipinde **gerçekten koşulup** kaydedilmiştir. + +## Neyi gösteriyor + +| Özellik | Nerede | +|---|---| +| `zamanlayici 1000ms:` ile saniyede bir güncellenen arayüz | pencerenin sonu | +| `sayilabilir Kip` (odak / mola) | dosyanın başı | +| f-string biçimi: `f"{dk:02d}:{sn:02d}"` → `07:05` | `saat_metni()` | +| **Canlı bağlama**: `"Şu an: " + GorevGirdisi.metin` | `etiket Baslik` | +| `acilir_liste` ile süre seçimi, metinden sayı ayrıştırma | `dakika_coz()` | +| `ilerleme_cubugu`, masaüstü bildirimi | zamanlayıcı bloğu | +| Ortam değişkeniyle deneme kipi | `cevre.al("ODAK_DENEME")` | diff --git a/arayuz/odak/odak.kbl b/arayuz/odak/odak.kbl new file mode 100644 index 0000000..beb18ab --- /dev/null +++ b/arayuz/odak/odak.kbl @@ -0,0 +1,155 @@ +# ╔══════════════════════════════════════════════════════════════╗ +# ║ Odak — KabaLang ile yazılmış Pomodoro zamanlayıcısı ║ +# ║ Odaklan, mola ver, günün oturumlarını kaydet. ║ +# ╚══════════════════════════════════════════════════════════════╝ +# +# Çalıştır: kabalang calistir odak.kbl +# Deneme kipi: ODAK_DENEME=1 kabalang calistir odak.kbl +# (1 dakika 1 saniye sürer — sunum ve sınama için) + +kullan kaba.arayuz +kullan kaba.sistem + +izinler: + ag_erisim = yanlis + +# ── Oturum türleri ──────────────────────────────────────────────── +sayilabilir Kip: + ODAK + MOLA + +fonksiyon dakika_coz(secim: metin) -> sayi: + # "Odak · 25 dk" → 25 + parca = secim.bol(" · ")[1] + don sayi(parca.degistir(" dk", "")) + +fonksiyon kip_coz(secim: metin) -> Kip: + eger secim.icerir("mola"): + don Kip.MOLA + don Kip.ODAK + +# 1500 → "25:00" +fonksiyon saat_metni(saniye: sayi) -> metin: + dk = saniye // 60 + sn = saniye % 60 + don f"{dk:02d}:{sn:02d}" + +fonksiyon durum_metni(k: Kip, calisiyor: mantik) -> metin: + eger degil calisiyor: + don "Hazır olduğunda başlat" + eger k == Kip.MOLA: + don "☕ Mola — biraz nefes al" + don "● Odaklanılıyor" + +# ── Durum ───────────────────────────────────────────────────────── +saniye_carpani = 1 +eger cevre.al("ODAK_DENEME") == "1": + saniye_carpani = 60 + +toplam = 25 * 60 +kalan = toplam +kip = Kip.ODAK +calisiyor = yanlis +oturum_sayisi = 0 +odak_dakikasi = 0 + +pencere Odak: + baslik = "Odak — Pomodoro" + genislik = 900 + yukseklik = 620 + tema = "koyu" + + yatay_kutu Govde: + dikey_kutu Zamanlayici: + girdi GorevGirdisi: + metin = "KabaLang örnekleri" + ipucu = "Ne üzerinde çalışıyorsun?" + stil = "font-size: 17px; padding: 10px 12px; border-radius: 10px; border: 1px solid #1c2336; background: #0d1220; color: #e6e9f2;" + + # Canlı bağlama: yukarıdaki girdiye yazdıkça bu satır kendiliğinden değişir. + etiket Baslik: + metin = "Şu an: " + GorevGirdisi.metin + stil = "font-size: 20px; font-weight: 700; color: #22d3ee; padding: 2px 4px 6px 4px;" + + acilir_liste Secim: + secenekler = ["Odak · 25 dk", "Derin çalışma · 50 dk", "Kısa mola · 5 dk", "Uzun mola · 15 dk"] + stil = "font-size: 17px; padding: 8px 12px; border-radius: 10px; background: #121829; color: #e6e9f2;" + degistiginde: + eger degil calisiyor: + kip = kip_coz(Secim.secili_metin) + toplam = dakika_coz(Secim.secili_metin) * 60 + kalan = toplam + Sayac.metin = saat_metni(kalan) + Ilerleme.deger = 0 + + etiket Sayac: + metin = "25:00" + stil = "font-size: 112px; font-weight: 800; color: #ffffff; padding: 10px 0px 0px 6px;" + + etiket Durum: + metin = "Hazır olduğunda başlat" + stil = "font-size: 18px; color: #9ece6a; padding: 0px 8px 10px 8px;" + + ilerleme_cubugu Ilerleme: + deger = 0 + stil = "font-size: 14px; min-height: 22px; border-radius: 11px; background: #121829; color: #e6e9f2;" + + yatay_kutu Dugmeler: + dugme Baslat: + metin = "Başlat" + stil = "font-size: 19px; font-weight: 700; min-height: 54px; border-radius: 14px; border: none; background: #377ef4; color: #ffffff;" + tiklandiginda: + calisiyor = degil calisiyor + eger calisiyor: + Baslat.metin = "Duraklat" + degilse: + Baslat.metin = "Sürdür" + Durum.metin = durum_metni(kip, calisiyor) + + dugme Sifirla: + metin = "Sıfırla" + stil = "font-size: 19px; font-weight: 600; min-height: 54px; border-radius: 14px; border: 1px solid #1c2336; background: #121829; color: #c9d1e6;" + tiklandiginda: + calisiyor = yanlis + kalan = toplam + Sayac.metin = saat_metni(kalan) + Ilerleme.deger = 0 + Baslat.metin = "Başlat" + Durum.metin = durum_metni(kip, calisiyor) + + dikey_kutu Gunluk: + etiket GunlukBasligi: + metin = "Bugün" + stil = "font-size: 20px; font-weight: 700; color: #22d3ee; padding: 6px 4px;" + + etiket Ozet: + metin = "0 oturum · 0 dk odak" + stil = "font-size: 26px; font-weight: 800; color: #ffffff; padding: 2px 4px 10px 4px;" + + liste Oturumlar: + stil = "font-size: 17px; color: #c9d1e6; background: #0d1220; border: 1px solid #1c2336; border-radius: 12px; padding: 8px; min-width: 330px;" + + # Saniyede bir: geri say, çubuğu ilerlet, bitince kaydet ve haber ver. + zamanlayici 1000ms: + eger calisiyor: + kalan = kalan - saniye_carpani + eger kalan < 0: + kalan = 0 + Sayac.metin = saat_metni(kalan) + Ilerleme.deger = (toplam - kalan) * 100 // toplam + eger kalan == 0: + calisiyor = yanlis + dakika = toplam // 60 + eger kip == Kip.ODAK: + oturum_sayisi = oturum_sayisi + 1 + odak_dakikasi = odak_dakikasi + dakika + Oturumlar.ekle(f"✓ {zaman.saat().parca(0, 5)} {GorevGirdisi.metin} · {dakika} dk") + sistem.bildirim("Odak", "Oturum bitti! Kısa bir mola hak ettin.") + degilse: + Oturumlar.ekle(f"☕ {zaman.saat().parca(0, 5)} Mola · {dakika} dk") + sistem.bildirim("Odak", "Mola bitti, kaldığın yerden devam.") + Ozet.metin = f"{oturum_sayisi} oturum · {odak_dakikasi} dk odak" + kalan = toplam + Sayac.metin = saat_metni(kalan) + Baslat.metin = "Başlat" + Durum.metin = "Tamamlandı — yenisine hazır" diff --git a/arayuz/parola-atolyesi/README.md b/arayuz/parola-atolyesi/README.md new file mode 100644 index 0000000..151c468 --- /dev/null +++ b/arayuz/parola-atolyesi/README.md @@ -0,0 +1,32 @@ +# Parola Atölyesi + +Güçlü parola üret, gücünü entropiyle ölç, SHA-256 özetini çıkar. Sağdaki +ölçer siz yazdıkça canlı çalışır. + +![Parola Atölyesi](../../medya/parola-atolyesi-ilk.png) +![Canlı ölçüm](../../medya/parola-atolyesi.png) + +```bash +kabalang calistir parola-atolyesi.kbl +``` + +## Neyi gösteriyor + +| Özellik | Nerede | +|---|---| +| **log₂'yi kendimiz hesaplıyoruz**: tam kısım bölmeyle, kesir kısmı kare alma yöntemiyle | `log2()` | +| Entropi = uzunluk × log₂(havuz) | `entropi()` | +| Seçilen her türden en az bir karakter (yeniden üretme yöntemi) | `parola_uret()` | +| Yaygın kalıp denetimi: `Ankara1923!` güçlü **görünür** ama değildir | `yaygin_kalip()` | +| **Canlı bağlama — fonksiyon çağrısıyla**: hem metin hem renk | `etiket DenemeSonucu` | +| Sürgüye bağlı etiket: `"Uzunluk: " + metin(Uzunluk.deger)` | `etiket UzunlukYazisi` | +| `kripto.sha256`, `tablo`, `onay_kutusu`, `kaydirici` | sol sütun | +| Geçmişte parolanın tamamı gösterilmez (`qP.~••••`) | `maskele()` | + +## Dürüst notlar + +- Yazılan parola için ölçülen değer bir **üst sınırdır** (`≤`): karakter + türlerinden hesaplanır, sözcük seçimini bilemez. +- Üretimde `rastgele` modülü kullanılır. Bu modül kriptografik bir rastgelelik + kaynağı değildir; örnek eğitim amaçlıdır. Gerçek parolalarınız için + kriptografik kaynak kullanan bir parola yöneticisi tercih edin. diff --git a/arayuz/parola-atolyesi/parola-atolyesi.kbl b/arayuz/parola-atolyesi/parola-atolyesi.kbl new file mode 100644 index 0000000..8d13af3 --- /dev/null +++ b/arayuz/parola-atolyesi/parola-atolyesi.kbl @@ -0,0 +1,329 @@ +# ╔══════════════════════════════════════════════════════════════╗ +# ║ Parola Atölyesi ║ +# ║ Güçlü parola üret, gücünü entropiyle ölç, özetini çıkar. ║ +# ║ Entropi hesabı (log₂ dahil) baştan sona KabaLang. ║ +# ╚══════════════════════════════════════════════════════════════╝ +# +# Çalıştır: kabalang calistir parola-atolyesi.kbl + +kullan kaba.arayuz +kullan kaba.sistem +kullan kaba.kripto + +# Ağa çıkmaz, diske yazmaz: bu beyan derleme anında denetlenir. +izinler: + ag_erisim = yanlis + +# ── 1. KARAKTER HAVUZLARI ───────────────────────────────────────── +KUCUK = "abcdefghijklmnopqrstuvwxyz" +BUYUK = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +RAKAM = "0123456789" +SEMBOL = "!@#$%^&*()-_=+[]{};:,.<>/?~" +BENZER = "0Oo1lI|" + +# Metni harf listesine ayırır (Türkçe harfler tek harf sayılır). +fonksiyon harfler(m: metin) -> liste: + sonuc = [""] + sonuc.sil(0) + i = 0 + iken i < m.uzunluk(): + sonuc.ekle(m.parca(i, 1)) + i = i + 1 + don sonuc + +fonksiyon benzerleri_cikar(havuz: metin) -> metin: + sonuc = "" + dongu c icinde harfler(havuz): + eger degil BENZER.icerir(c): + sonuc = sonuc + c + don sonuc + +fonksiyon havuz_kur(kucuk: mantik, buyuk: mantik, rakam: mantik, sembol: mantik, sade: mantik) -> metin: + h = "" + eger kucuk: + h = h + KUCUK + eger buyuk: + h = h + BUYUK + eger rakam: + h = h + RAKAM + eger sembol: + h = h + SEMBOL + eger sade: + h = benzerleri_cikar(h) + don h + +# ── 2. ENTROPİ: log₂'yi kendimiz hesaplıyoruz ───────────────────── +# Tam kısım: 2'ye bölerek sayılır. Kesir kısmı: kare alma yöntemiyle, +# her adımda bir ikili basamak (bit) bulunur. +fonksiyon log2(x: ondalik) -> ondalik: + tam = 0.0 + iken x >= 2.0: + x = x / 2.0 + tam = tam + 1.0 + kesir = 0.0 + adim = 0.5 + dongu i icinde 1..24: + x = x * x + eger x >= 2.0: + x = x / 2.0 + kesir = kesir + adim + adim = adim / 2.0 + don tam + kesir + +fonksiyon entropi(uzunluk: sayi, havuz_boyu: sayi) -> sayi: + eger havuz_boyu < 2: + don 0 + don sayi(matematik.taban(ondalik(uzunluk) * log2(ondalik(havuz_boyu)))) + +# Kullanıcının yazdığı parolanın havuzunu, içerdiği karakter türlerinden tahmin et. +fonksiyon tahmini_havuz(p: metin) -> sayi: + var_kucuk = yanlis + var_buyuk = yanlis + var_rakam = yanlis + var_sembol = yanlis + dongu c icinde harfler(p): + eger KUCUK.icerir(c): + var_kucuk = dogru + degilse eger BUYUK.icerir(c): + var_buyuk = dogru + degilse eger RAKAM.icerir(c): + var_rakam = dogru + degilse: + var_sembol = dogru + toplam = 0 + eger var_kucuk: + toplam = toplam + 26 + eger var_buyuk: + toplam = toplam + 26 + eger var_rakam: + toplam = toplam + 10 + eger var_sembol: + toplam = toplam + 33 + don toplam + +sayilabilir Guc: + ZAYIF + ORTA + GUCLU + COK_GUCLU + +fonksiyon guc(bit: sayi) -> Guc: + eger bit < 40: + don Guc.ZAYIF + degilse eger bit < 60: + don Guc.ORTA + degilse eger bit < 90: + don Guc.GUCLU + don Guc.COK_GUCLU + +fonksiyon guc_adi(g: Guc) -> metin: + eger g == Guc.ZAYIF: + don "Zayıf" + degilse eger g == Guc.ORTA: + don "Orta" + degilse eger g == Guc.GUCLU: + don "Güçlü" + don "Çok güçlü" + +fonksiyon guc_rengi(g: Guc) -> metin: + eger g == Guc.ZAYIF: + don "#f7768e" + degilse eger g == Guc.ORTA: + don "#e0af68" + degilse eger g == Guc.GUCLU: + don "#9ece6a" + don "#22d3ee" + +fonksiyon guc_yuzde(bit: sayi) -> sayi: + eger bit >= 128: + don 100 + don bit * 100 // 128 + +# Saldırganların ilk denediği kalıplar. Bunlardan biri varsa +# karakter çeşitliliği yanıltıcıdır: "Ankara1923!" güçlü GÖRÜNÜR. +YAYGIN_KALIPLAR = ["123456", "qwerty", "parola", "sifre", "password", "admin", + "ankara", "istanbul", "izmir", "1923", "1453", "galatasaray", + "fenerbahce", "besiktas", "trabzon", "asdf", "1234", "0000"] + +fonksiyon yaygin_kalip(p: metin) -> metin: + kucuk = p.kucult() + dongu k icinde YAYGIN_KALIPLAR: + eger kucuk.icerir(k): + don k + don "" + +fonksiyon olcum_biti(p: metin) -> sayi: + bit = entropi(p.uzunluk(), tahmini_havuz(p)) + eger yaygin_kalip(p) != "": + bit = bit // 3 + don bit + +# Yazılan parolada ölçtüğümüz şey bir ÜST SINIRDIR: karakter türlerinden +# hesaplanır, sözcük seçimini bilemez. Bu yüzden "≤" ile gösterilir. +fonksiyon olcum_metni(p: metin) -> metin: + eger p == "": + don "Bir parola yaz, gücünü canlı ölçelim" + bit = olcum_biti(p) + kalip = yaygin_kalip(p) + eger kalip != "": + don f"{guc_adi(guc(bit))} · ≤{bit} bit · tahmin edilebilir: \"{kalip}\"" + don f"{guc_adi(guc(bit))} · ≤{bit} bit · {p.uzunluk()} karakter" + +fonksiyon olcum_stili(p: metin) -> metin: + don f"font-size: 17px; font-weight: 700; color: {guc_rengi(guc(olcum_biti(p)))}; padding: 4px 2px 12px 2px;" + +# Geçmişte parolanın tamamı gösterilmez: "qP.~z{2x" → "qP.~••••" +fonksiyon maskele(p: metin) -> metin: + eger p.uzunluk() <= 4: + don "••••" + don p.parca(0, 4) + "••••" + +# ── 3. ÜRETİM ───────────────────────────────────────────────────── +fonksiyon tur_var_mi(p: metin, havuz: metin) -> mantik: + dongu c icinde harfler(p): + eger havuz.icerir(c): + don dogru + don yanlis + +# Seçilen her türden en az bir karakter gelene kadar yeniden üret. +# (Eksik karakteri sonradan yerleştirmek yerine baştan üretmek +# dağılımı bozmaz.) +fonksiyon parola_uret(uzunluk: sayi, kucuk: mantik, buyuk: mantik, rakam: mantik, sembol: mantik, sade: mantik) -> metin: + havuz = havuz_kur(kucuk, buyuk, rakam, sembol, sade) + eger havuz == "": + don "" + iken dogru: + p = rastgele.metin(uzunluk, havuz) + tamam = dogru + eger kucuk ve degil tur_var_mi(p, KUCUK): + tamam = yanlis + eger buyuk ve degil tur_var_mi(p, BUYUK): + tamam = yanlis + eger rakam ve degil tur_var_mi(p, RAKAM): + tamam = yanlis + eger sembol ve degil tur_var_mi(p, SEMBOL): + tamam = yanlis + eger tamam: + don p + don "" + +ilk_acilis = dogru + +pencere ParolaAtolyesi: + baslik = "Parola Atölyesi" + genislik = 980 + yukseklik = 680 + tema = "koyu" + + yatay_kutu Govde: + # ── SOL: üretici ────────────────────────────────────────── + dikey_kutu Uretici: + etiket Baslik: + metin = "Yeni parola" + stil = "font-size: 22px; font-weight: 800; color: #ffffff; padding: 4px 2px;" + + kaydirici Uzunluk: + min = 8 + max = 64 + deger = 20 + + # Canlı bağlama: sürgü hareket ettikçe yazı kendiliğinden güncellenir. + etiket UzunlukYazisi: + metin = "Uzunluk: " + metin(Uzunluk.deger) + " karakter" + stil = "font-size: 16px; color: #22d3ee; padding: 0px 2px 6px 2px;" + + onay_kutusu Kucuk: + metin = "Küçük harf (a–z)" + secili = dogru + stil = "font-size: 16px; color: #c9d1e6;" + onay_kutusu Buyuk: + metin = "Büyük harf (A–Z)" + secili = dogru + stil = "font-size: 16px; color: #c9d1e6;" + onay_kutusu Rakam: + metin = "Rakam (0–9)" + secili = dogru + stil = "font-size: 16px; color: #c9d1e6;" + onay_kutusu Sembol: + metin = "Sembol (!@#$…)" + secili = dogru + stil = "font-size: 16px; color: #c9d1e6;" + onay_kutusu Sade: + metin = "Karışan karakterleri çıkar (0 O o 1 l I)" + secili = dogru + stil = "font-size: 16px; color: #c9d1e6;" + + dugme Uret: + metin = "Parola üret" + stil = "font-size: 19px; font-weight: 700; min-height: 54px; border-radius: 14px; border: none; background: #377ef4; color: #ffffff;" + tiklandiginda: + p = parola_uret(Uzunluk.deger, Kucuk.secili, Buyuk.secili, Rakam.secili, Sembol.secili, Sade.secili) + eger p == "": + uyari_kutusu("Parola Atölyesi", "En az bir karakter türü seçin.") + degilse: + bit = entropi(p.uzunluk(), havuz_kur(Kucuk.secili, Buyuk.secili, Rakam.secili, Sembol.secili, Sade.secili).uzunluk()) + g = guc(bit) + Parola.metin = p + GucCubugu.deger = guc_yuzde(bit) + GucYazisi.metin = f"{guc_adi(g)} · {bit} bit entropi" + GucYazisi.stil = f"font-size: 17px; font-weight: 700; color: {guc_rengi(g)}; padding: 2px;" + Ozet.metin = "SHA-256 " + kripto.sha256(p).parca(0, 32) + "…" + Gecmis.satir_ekle([zaman.saat(), maskele(p), metin(p.uzunluk()), f"{bit} bit", guc_adi(g)]) + + etiket Parola: + metin = "—" + stil = "font-size: 21px; font-weight: 700; font-family: monospace; color: #ffffff; background: #0d1220; border: 1px solid #1c2336; border-radius: 12px; padding: 14px;" + + ilerleme_cubugu GucCubugu: + deger = 0 + stil = "font-size: 13px; min-height: 18px; border-radius: 9px; background: #121829; color: #e6e9f2;" + + etiket GucYazisi: + metin = "" + stil = "font-size: 17px; font-weight: 700; color: #9ece6a; padding: 2px;" + + etiket Ozet: + metin = "" + stil = "font-size: 14px; font-family: monospace; color: #7d8aa8; padding: 2px;" + + # ── SAĞ: ölçer ve geçmiş ────────────────────────────────── + dikey_kutu Olcer: + etiket OlcerBasligi: + metin = "Kendi parolanı ölç" + stil = "font-size: 22px; font-weight: 800; color: #ffffff; padding: 4px 2px;" + + girdi Deneme: + metin = "Ankara1923!" + ipucu = "Bir parola yaz…" + stil = "font-size: 18px; font-family: monospace; padding: 10px 12px; border-radius: 10px; border: 1px solid #1c2336; background: #0d1220; color: #e6e9f2;" + + # Bağlama bir FONKSİYON ÇAĞRISI da olabilir: her tuşta yeniden ölçülür. + etiket DenemeSonucu: + metin = olcum_metni(Deneme.metin) + stil = olcum_stili(Deneme.metin) + + etiket GecmisBasligi: + metin = "Bu oturumda üretilenler" + stil = "font-size: 17px; font-weight: 700; color: #22d3ee; padding: 6px 2px;" + + tablo Gecmis: + sutunlar = ["Saat", "Parola", "Uzunluk", "Entropi", "Güç"] + stil = "font-size: 15px;" + + # Açılışta üç gerçek parola üretilir: farklı uzunluk ve türlerle. + zamanlayici 50ms: + eger ilk_acilis: + ilk_acilis = yanlis + dongu ayar icinde [12, 16, 32]: + p = parola_uret(ayar, dogru, dogru, dogru, ayar != 16, dogru) + bit = entropi(p.uzunluk(), havuz_kur(dogru, dogru, dogru, ayar != 16, dogru).uzunluk()) + Gecmis.satir_ekle([zaman.saat(), maskele(p), metin(p.uzunluk()), f"{bit} bit", guc_adi(guc(bit))]) + p = parola_uret(Uzunluk.deger, Kucuk.secili, Buyuk.secili, Rakam.secili, Sembol.secili, Sade.secili) + bit = entropi(p.uzunluk(), havuz_kur(Kucuk.secili, Buyuk.secili, Rakam.secili, Sembol.secili, Sade.secili).uzunluk()) + g = guc(bit) + Parola.metin = p + GucCubugu.deger = guc_yuzde(bit) + GucYazisi.metin = f"{guc_adi(g)} · {bit} bit entropi" + GucYazisi.stil = f"font-size: 17px; font-weight: 700; color: {guc_rengi(g)}; padding: 2px;" + Ozet.metin = "SHA-256 " + kripto.sha256(p).parca(0, 32) + "…" + Gecmis.satir_ekle([zaman.saat(), maskele(p), metin(p.uzunluk()), f"{bit} bit", guc_adi(g)]) diff --git a/konsol/fatura/README.md b/konsol/fatura/README.md new file mode 100644 index 0000000..1ce2bf6 --- /dev/null +++ b/konsol/fatura/README.md @@ -0,0 +1,39 @@ +# Fatura — JSON siparişten terminal tablosu, PDF ve Excel + +![Fatura](../../medya/fatura.png) + +`siparis.json`'u okur; her kalem için indirim ve KDV hesaplar, KDV'yi +oranlara göre döker ve sonucu üç biçimde verir: + +1. Terminalde kutu çizgili, renkli bir fatura tablosu +2. `fatura.pdf` — Türkçe karakterleri ve `₺` işaretini taşıyan, sayfa + numaralı PDF +3. `fatura.xlsx` — tablolar ayrı çalışma sayfalarında + +```bash +kabalang calistir fatura.kbl # siparis.json → ./fatura.pdf + ./fatura.xlsx +kabalang calistir fatura.kbl -- baska.json --cikti ~/Belgeler +``` + +## KabaLang'ın gösterdikleri + +| Özellik | Nerede | +|---|---| +| `yapi` + metotlar | `Kalem.brut()`, `matrah()`, `kdv_tutari()` — hesap verinin yanında | +| `sonuc` | `siparis_oku` dosya yokken ya da JSON bozukken **değerle** hata döner; yok sayılamaz | +| `dene / yakala` | JSON dizisini sonuna kadar gezmek | +| `json` | İç içe erişim: `v["satici"]["ad"]`, `v["kalemler"][i]["adet"]` | +| `kaba.belge` | Aynı belge nesnesinden `pdf_yaz` ve `xlsx_yaz` | +| f-string biçimi | `{ad:<32}`, `{tutar:>15}`, `{k % 100:02d}` — hizalı tablo ve kuruş | + +## Para nasıl tutuluyor + +Her ara sonuç **kuruşa yuvarlanır** (`kurus()`); `0.1 + 0.2` gibi ikili kesir +artıkları faturaya taşınmaz. `para()` sayıyı Türkçe biçime çevirir: +`1249.9 → 1.249,90 ₺`. + +Örnek siparişin toplamları elle de hesaplanıp karşılaştırıldı: +ara toplam 65.439,80 ₺ · indirim 1.879,99 ₺ · KDV 11.748,96 ₺ · +**genel toplam 75.308,77 ₺**. + +> Satıcı ve alıcı bilgileri örnek amaçlı, hayalidir. diff --git a/konsol/fatura/fatura.kbl b/konsol/fatura/fatura.kbl new file mode 100644 index 0000000..08dc068 --- /dev/null +++ b/konsol/fatura/fatura.kbl @@ -0,0 +1,179 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ FATURA — JSON siparişten KDV'li fatura: terminal + PDF + Excel ║ +# ║ ║ +# ║ siparis.json'u okur, satır satır indirim ve KDV hesaplar, ║ +# ║ oranlara göre KDV dökümü çıkarır; sonucu terminalde kutu ║ +# ║ çizgili bir tablo olarak basar ve aynı veriden fatura.pdf ile ║ +# ║ fatura.xlsx üretir. Para kuruşa yuvarlanarak tutulur. ║ +# ║ ║ +# ║ kabalang calistir fatura.kbl ║ +# ║ kabalang calistir fatura.kbl -- baska.json --cikti ~/Belgeler ║ +# ╚══════════════════════════════════════════════════════════════════╝ +kullan kaba.sistem +kullan kaba.belge + +yapi Kalem: + ad = "" + adet = 0 + birim_fiyat = 0.0 + indirim = 0 # yüzde + kdv = 0 # yüzde + + fonksiyon brut() -> ondalik: + don kurus(birim_fiyat * ondalik(adet)) + fonksiyon matrah() -> ondalik: + don kurus(brut() * (1.0 - ondalik(indirim) / 100.0)) + fonksiyon kdv_tutari() -> ondalik: + don kurus(matrah() * ondalik(kdv) / 100.0) + +# Kuruşa yuvarla: 0.1 + 0.2 gibi ikili kesir artıklarını faturaya taşıma. +fonksiyon kurus(x: ondalik) -> ondalik: + don matematik.yuvarla(x * 100.0) / 100.0 + +# 1249.9 → "1.249,90 ₺" — Türkçe binlik ve ondalık ayıracı. +fonksiyon para(x: ondalik) -> metin: + k = sayi(matematik.yuvarla(x * 100.0)) + tam = metin(k // 100) + gruplu = "" + i = tam.uzunluk() + iken i > 3: + gruplu = "." + tam.dilim(i - 3, 3) + gruplu + i = i - 3 + gruplu = tam.dilim(0, i) + gruplu + don f"{gruplu},{k % 100:02d} ₺" + +# ── Siparişi oku ───────────────────────────────────────────────────── +# Beklenen hata (dosya yok, bozuk JSON) dönüş değeriyle bildirilir. +fonksiyon siparis_oku(yol: metin) -> sonuc: + eger degil dosya.var_mi(yol): + don hata(f"sipariş dosyası bulunamadı: {yol}") + ham = dosya.oku(yol) + eger degil json.gecerli_mi(ham): + don hata(f"{yol} geçerli bir JSON değil") + don tamam(ham) + +# ── Terminal tablosu ───────────────────────────────────────────────── +fonksiyon boya(yazi: metin, rgb: metin) -> metin: + don "\033[38;2;" + rgb + "m" + yazi + "\033[0m" + +fonksiyon cizgi(sol: metin, orta: metin, sag: metin) -> metin: + don boya(" " + sol + "━".tekrarla(4) + orta + "━".tekrarla(34) + orta + "━".tekrarla(6) + orta + "━".tekrarla(15) + orta + "━".tekrarla(6) + orta + "━".tekrarla(5) + orta + "━".tekrarla(16) + sag, "70;80;110") + +fonksiyon satir(no: metin, ad: metin, adet: metin, birim: metin, ind: metin, kdv: metin, tutar: metin) -> metin: + c = boya("┃", "70;80;110") + don f" {c}{no:>3} {c} {ad:<32} {c}{adet:>5} {c}{birim:>14} {c}{ind:>5} {c}{kdv:>4} {c}{tutar:>15} {c}" + +# ── Program ────────────────────────────────────────────────────────── +ks = KomutSatiri("fatura", "JSON siparişten KDV'li fatura üretir") +ks.konumsal("siparis", "Sipariş dosyası (varsayılan: siparis.json)") +ks.secenek("cikti", "c", "PDF ve XLSX'in yazılacağı klasör") +c = ks.coz() +eger c.yardim_mi(): + c.yardim_yaz() + cikis(0) +eger c.hatali_mi(): + c.hata_yaz_ve_cik() + +okunan = siparis_oku(c.konumsal("siparis").deger_veya("siparis.json")) +eger okunan.hatali_mi(): + konsol.hata_yaz("✗ " + okunan.hata().mesaj() + "\n") + cikis(1) +v = json.coz(okunan.deger()) + +# JSON dizisinin uzunluğu sorulamıyor; sınırın dışına çıkınca biter. +kalemler = [Kalem()] # boş yapı listesi: bir öğeyle kurup sil +kalemler.sil(0) +i = 0 +bitti = yanlis +iken degil bitti: + dene: + j = v["kalemler"][i] + kalemler.ekle(Kalem(metin(j["ad"]), sayi(j["adet"]), ondalik(j["birim_fiyat"]), sayi(j["indirim"]), sayi(j["kdv"]))) + i = i + 1 + yakala: + bitti = dogru + +# ── Hesap ──────────────────────────────────────────────────────────── +ara_toplam = 0.0 +indirim_toplam = 0.0 +kdv_toplam = 0.0 +dongu k icinde kalemler: + ara_toplam = ara_toplam + k.brut() + indirim_toplam = indirim_toplam + k.brut() - k.matrah() + kdv_toplam = kdv_toplam + k.kdv_tutari() +matrah_toplam = ara_toplam - indirim_toplam +genel_toplam = kurus(matrah_toplam + kdv_toplam) + +# ── Terminal ───────────────────────────────────────────────────────── +no = metin(v["fatura_no"]) +tarih = metin(v["tarih"]) +satici = metin(v["satici"]["ad"]) +alici = metin(v["alici"]["ad"]) +yazdir("") +yazdir(boya(f" ◆ FATURA {no}", "125;170;255") + boya(f" {tarih}", "110;120;150")) +yazdir(boya(f" {satici} → {alici}", "200;208;224")) +yazdir(cizgi("┏", "┳", "┓")) +yazdir(satir("#", "Ürün / hizmet", "Adet", "Birim fiyat", "İnd.", "KDV", "Tutar")) +yazdir(cizgi("┣", "╋", "┫")) +s = 1 +dongu k icinde kalemler: + ind = "—" + eger k.indirim > 0: + ind = f"%{k.indirim}" + yazdir(satir(metin(s), k.ad, metin(k.adet), para(k.birim_fiyat), ind, f"%{k.kdv}", para(k.matrah()))) + s = s + 1 +yazdir(cizgi("┗", "┻", "┛")) + +fonksiyon toplam_satiri(ad: metin, deger: metin, rgb: metin) -> metin: + don boya(f"{ad:>78}", "150;160;185") + boya(f"{deger:>17}", rgb) + +yazdir(toplam_satiri("Ara toplam", para(ara_toplam), "200;208;224")) +yazdir(toplam_satiri("İndirim", "−" + para(indirim_toplam), "248;113;113")) +yazdir(toplam_satiri("KDV matrahı", para(matrah_toplam), "200;208;224")) +dongu oran icinde [20, 10, 1]: + o_matrah = 0.0 + o_kdv = 0.0 + dongu k icinde kalemler: + eger k.kdv == oran: + o_matrah = o_matrah + k.matrah() + o_kdv = o_kdv + k.kdv_tutari() + eger o_kdv > 0.0: + yazdir(toplam_satiri(f"KDV %{oran} ({para(o_matrah)} üzerinden)", para(o_kdv), "200;208;224")) +yazdir(toplam_satiri("GENEL TOPLAM", para(genel_toplam), "74;222;128")) + +# ── Belge: aynı veriden PDF + Excel ────────────────────────────────── +klasor = c.secenek("cikti").deger_veya(".") +b = belge.yeni(f"Fatura {no}") +belge.altbilgi(b, f"{satici} · {no}") +belge.baslik(b, "FATURA", 1) +belge.paragraf(b, f"Fatura no: {no} Tarih: {tarih}") +belge.baslik(b, "Satıcı", 3) +belge.paragraf(b, satici + " — " + metin(v["satici"]["adres"]) + " — " + metin(v["satici"]["vergi"])) +belge.baslik(b, "Alıcı", 3) +belge.paragraf(b, alici + " — " + metin(v["alici"]["adres"]) + " — " + metin(v["alici"]["vergi"])) +belge.baslik(b, "Kalemler", 2) +belge.tablo_basligi(b, ["#", "Ürün / hizmet", "Miktar × fiyat", "İnd. / KDV", "Tutar"]) +s = 1 +dongu k icinde kalemler: + ind = "—" + eger k.indirim > 0: + ind = f"%{k.indirim}" + belge.tablo_satiri(b, [metin(s), metin(k.ad), f"{k.adet} × {para(k.birim_fiyat)}", f"{ind} / %{k.kdv}", para(k.matrah())]) + s = s + 1 +belge.baslik(b, "Özet", 2) +belge.tablo_basligi(b, ["Kalem", "Tutar"]) +belge.tablo_satiri(b, ["Ara toplam", para(ara_toplam)]) +belge.tablo_satiri(b, ["İndirim", "-" + para(indirim_toplam)]) +belge.tablo_satiri(b, ["KDV matrahı", para(matrah_toplam)]) +belge.tablo_satiri(b, ["KDV toplamı", para(kdv_toplam)]) +belge.tablo_satiri(b, ["GENEL TOPLAM", para(genel_toplam)]) +belge.vurgu(b, f"Ödenecek tutar: {para(genel_toplam)}") + +yazdir("") +eger belge.pdf_yaz(b, klasor + "/fatura.pdf"): + yazdir(boya(f" ✓ {klasor}/fatura.pdf yazıldı", "74;222;128")) +degilse: + yazdir(boya(" ✗ PDF yazılamadı", "248;113;113")) +eger belge.xlsx_yaz(b, klasor + "/fatura.xlsx"): + yazdir(boya(f" ✓ {klasor}/fatura.xlsx yazıldı", "74;222;128")) +belge.kapat(b) diff --git a/konsol/fatura/siparis.json b/konsol/fatura/siparis.json new file mode 100644 index 0000000..cee31dc --- /dev/null +++ b/konsol/fatura/siparis.json @@ -0,0 +1,22 @@ +{ + "fatura_no": "A37-2026-000142", + "tarih": "2026-10-02", + "satici": { + "ad": "Atölye 37 Bilişim Ltd. Şti.", + "adres": "Yıldız Teknopark, B Blok No:7, İstanbul", + "vergi": "Boğaziçi VD · 1234567890" + }, + "alici": { + "ad": "Mavi Deniz Kafe ve Ortak Çalışma Alanı", + "adres": "Kordon Boyu Cad. No:12, Konak / İzmir", + "vergi": "Konak VD · 9876543210" + }, + "kalemler": [ + { "ad": "Mekanik klavye (Türkçe Q)", "adet": 4, "birim_fiyat": 2450.00, "indirim": 10, "kdv": 20 }, + { "ad": "27\" IPS monitör", "adet": 2, "birim_fiyat": 8999.90, "indirim": 5, "kdv": 20 }, + { "ad": "Ergonomik çalışma sandalyesi", "adet": 3, "birim_fiyat": 6340.00, "indirim": 0, "kdv": 20 }, + { "ad": "Yazılım bakım hizmeti (aylık)", "adet": 1, "birim_fiyat": 12500.00, "indirim": 0, "kdv": 20 }, + { "ad": "Toplantı ikramı (kişi başı)", "adet": 12, "birim_fiyat": 185.00, "indirim": 0, "kdv": 10 }, + { "ad": "Kahve çekirdeği 1 kg", "adet": 5, "birim_fiyat": 780.00, "indirim": 0, "kdv": 1 } + ] +} diff --git a/konsol/fraktal/README.md b/konsol/fraktal/README.md new file mode 100644 index 0000000..97a7513 --- /dev/null +++ b/konsol/fraktal/README.md @@ -0,0 +1,39 @@ +# Fraktal — terminalde 24 bit renkli Mandelbrot ve Julia + +![Fraktal](../../medya/fraktal.png) + +Terminali bir tuvale çevirir. Her karakter hücresi **iki piksel** taşır: +üst yarım blok `▀` ön plan rengiyle üst pikseli, arka plan rengiyle alt +pikseli çizer. 100 sütunluk bir terminal böylece 100 × 72 piksellik bir +görüntü olur. + +```bash +kabalang calistir fraktal.kbl # Mandelbrot kümesi +kabalang calistir fraktal.kbl -- --tur julia # Julia kümesi (c = -0.8 + 0.156i) +kabalang calistir fraktal.kbl -- --yakinlas # denizatı vadisine 120 karelik dalış +kabalang calistir fraktal.kbl -- -s 160 -r 44 # geniş terminal: 160 sütun × 44 satır +``` + +## KabaLang'ın gösterdikleri + +| Özellik | Nerede | +|---|---| +| `birlikte:` + `baslat` | Görüntü dört şeride bölünür, şeritler **aynı anda** hesaplanır ve sırası korunarak birleştirilir (`kare_ciz`) | +| `sayilabilir` | `Kume.MANDELBROT` / `Kume.JULIA` — kapalı değer kümesi | +| `yapi` | `Kamera` (merkez, görüş genişliği, yineleme sınırı) ve palet `Durak`ları | +| f-string biçimi | `{kam.merkez_x:.5f}{kam.merkez_y:+.5f}i` — işaretli ve hassasiyetli sayı | +| `KomutSatiri` | `--tur`, `--yakinlas`, `--sutun`/`--satir` ve kendiliğinden üretilen `--yardim` | +| Tipli imzalar | `fonksiyon kacis(zx: ondalik, ...) -> ondalik` | + +## Nasıl çalışır + +* **Kaçış süresi:** `z ← z² + c` yinelenir; `|z|` 2'yi aşarsa nokta kümeden + kaçmıştır. Ne kadar çabuk kaçtığı rengini belirler, hiç kaçmayan nokta + kümenin içidir (lacivert). +* **Palet:** altı renk durağından (derin uzay → gece mavisi → mavi → + camgöbeği → lavanta → beyaz) bir kez 256 tonluk bir liste üretilir. +* **Derin yakınlaşma:** renk, yineleme sayısının üçgen dalgasıdır; böylece + 20 yinelemede de 700 yinelemede de paletin tamamı kullanılır. + +Bir kare (7.200 piksel, 120 yineleme sınırı) birkaç milisaniyede hesaplanır; +süre başlık satırında yazar. diff --git a/konsol/fraktal/fraktal.kbl b/konsol/fraktal/fraktal.kbl new file mode 100644 index 0000000..79171e8 --- /dev/null +++ b/konsol/fraktal/fraktal.kbl @@ -0,0 +1,171 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ FRAKTAL — terminalde 24 bit renkli Mandelbrot ve Julia kümesi ║ +# ║ ║ +# ║ Her karakter hücresi İKİ piksel taşır: üst yarım blok `▀`'nin ║ +# ║ ön plan rengi üst pikseli, arka plan rengi alt pikseli çizer. ║ +# ║ Görüntü dört şeride bölünür ve şeritler AYNI ANDA hesaplanır. ║ +# ║ ║ +# ║ kabalang calistir fraktal.kbl → Mandelbrot ║ +# ║ kabalang calistir fraktal.kbl --tur julia → Julia kümesi ║ +# ║ kabalang calistir fraktal.kbl --yakinlas → derinlere dalış║ +# ╚══════════════════════════════════════════════════════════════════╝ +kullan kaba.sistem + +sayilabilir Kume: + MANDELBROT + JULIA + +# Kameranın baktığı yer: karmaşık düzlemde merkez ve görüş genişliği. +yapi Kamera: + merkez_x = -0.65 + merkez_y = 0.0 + genislik = 3.2 + sinir = 120 # en fazla kaç yineleme + +# ── Renk ───────────────────────────────────────────────────────────── +# Palet, renk duraklarından bir kez üretilir: derin uzay → gece mavisi → +# KabaForce mavisi → camgöbeği → lavanta → beyaz. Aradaki 256 ton doğrusal +# karışımdır; her piksel yalnız listeden bir ton seçer. +yapi Durak: + konum = 0.0 + r = 0 + g = 0 + b = 0 + +fonksiyon karistir(a: sayi, b: sayi, t: ondalik) -> sayi: + don a + sayi(ondalik(b - a) * t) + +fonksiyon palet_uret() -> liste: + duraklar = [Durak(0.00, 10, 13, 21), Durak(0.18, 26, 42, 108), + Durak(0.40, 55, 126, 244), Durak(0.62, 34, 211, 238), + Durak(0.82, 167, 139, 250), Durak(1.00, 250, 245, 255)] + palet = [] # boş liste sabiti metin listesidir + i = 0 + iken i < 256: + t = ondalik(i) / 255.0 + k = 0 + iken t > duraklar[k + 1].konum: + k = k + 1 + a = duraklar[k] + b = duraklar[k + 1] + oran = (t - a.konum) / (b.konum - a.konum) + palet.ekle(f"{karistir(a.r, b.r, oran)};{karistir(a.g, b.g, oran)};{karistir(a.b, b.b, oran)}") + i = i + 1 + don palet + +PALET = palet_uret() + +fonksiyon renk(t: ondalik) -> metin: + eger t < 0.0: # kümenin içi: derin uzay laciverti + don "6;8;14" + don PALET[sayi(t * 255.0)] + +# ── Kaçış süresi ───────────────────────────────────────────────────── +# z ← z² + c. |z| 2'yi aşarsa nokta kümeden kaçmıştır; ne kadar +# çabuk kaçtığı rengini belirler. Hiç kaçmazsa -1 döner (kümenin içi). +# Renk yineleme sayısının üçgen dalgasıdır (0 → 1 → 0, her 80 adımda): +# böylece 20 yinelemede de 500 yinelemede de palet baştan sona kullanılır. +fonksiyon kacis(zx: ondalik, zy: ondalik, cx: ondalik, cy: ondalik, sinir: sayi) -> ondalik: + i = 0 + iken i < sinir: + x2 = zx * zx + y2 = zy * zy + eger x2 + y2 > 4.0: + dalga = ondalik(i % 80) / 40.0 + eger dalga > 1.0: + dalga = 2.0 - dalga + don matematik.karekok(dalga) + zy = 2.0 * zx * zy + cy + zx = x2 - y2 + cx + i = i + 1 + don -1.0 + +fonksiyon piksel(kume: Kume, kam: Kamera, px: sayi, py: sayi, sutun: sayi, satir: sayi) -> ondalik: + # Terminal hücresi kare değil; iki piksel ≈ bir sütun genişliği. + olcek = kam.genislik / ondalik(sutun) + x = kam.merkez_x + (ondalik(px) - ondalik(sutun) / 2.0) * olcek + y = kam.merkez_y + (ondalik(py) - ondalik(satir)) * olcek + eger kume == Kume.JULIA: + don kacis(x, y, -0.8, 0.156, kam.sinir) + don kacis(0.0, 0.0, x, y, kam.sinir) + +# Bir şerit: [bas, son) arasındaki karakter satırlarını tek metin olarak üretir. +fonksiyon serit(kume: Kume, kam: Kamera, bas: sayi, son: sayi, sutun: sayi, satir: sayi) -> metin: + cikti = "" + j = bas + iken j < son: + px = 0 + iken px < sutun: + ust = renk(piksel(kume, kam, px, 2 * j, sutun, satir)) + alt = renk(piksel(kume, kam, px, 2 * j + 1, sutun, satir)) + cikti = cikti + "\033[38;2;" + ust + "m\033[48;2;" + alt + "m▀" + px = px + 1 + cikti = cikti + "\033[0m\n" + j = j + 1 + don cikti + +# Dört şeridi dört görevde birlikte hesapla; sıra korunarak birleştir. +fonksiyon kare_ciz(kume: Kume, kam: Kamera, sutun: sayi, satir: sayi) -> metin: + d = satir // 4 + resim = "" # birlikte: kendi kapsamıdır, sonucu dışarıda tut + birlikte: + a = baslat serit(kume, kam, 0, d, sutun, satir) + b = baslat serit(kume, kam, d, 2 * d, sutun, satir) + c = baslat serit(kume, kam, 2 * d, 3 * d, sutun, satir) + e = baslat serit(kume, kam, 3 * d, satir, sutun, satir) + resim = a.bekle() + b.bekle() + c.bekle() + e.bekle() + don resim + +fonksiyon baslik(kume: Kume, kam: Kamera, sure: sayi) -> metin: + ad = "MANDELBROT" + eger kume == Kume.JULIA: + ad = "JULIA c = -0.8 + 0.156i" + yakinlik = 3.2 / kam.genislik + don f"\033[1;38;2;125;170;255m ◆ {ad}\033[0m\033[38;2;110;120;150m · merkez {kam.merkez_x:.5f}{kam.merkez_y:+.5f}i · ×{yakinlik:.1f} · {kam.sinir} yineleme · {sure} ms\033[0m" + +# ── Komut satırı ───────────────────────────────────────────────────── +ks = KomutSatiri("fraktal", "Terminalde 24 bit renkli fraktal çizer") +ks.secenek("tur", "t", "mandelbrot ya da julia") +ks.bayrak("yakinlas", "y", "Mandelbrot'un derinliklerine kare kare dal") +ks.secenek("sutun", "s", "çizim genişliği (karakter, varsayılan 100)") +ks.secenek("satir", "r", "çizim yüksekliği (karakter satırı, varsayılan 36)") +c = ks.coz() +eger c.yardim_mi(): + c.yardim_yaz() + cikis(0) +eger c.hatali_mi(): + c.hata_yaz_ve_cik() + +SUTUN = sayi(c.secenek("sutun").deger_veya("100")) +SATIR = sayi(c.secenek("satir").deger_veya("36")) # 36 karakter satırı = 72 piksel satırı +eger SUTUN < 8 veya SATIR < 4: + konsol.hata_yaz("fraktal: --sutun en az 8, --satir en az 4 olmalı\n") + cikis(2) + +kume = Kume.MANDELBROT +kam = Kamera() +eger c.secenek("tur").deger_veya("mandelbrot") == "julia": + kume = Kume.JULIA + kam.merkez_x = 0.0 + kam.genislik = 3.0 + +eger c.bayrak("yakinlas"): + # Denizatı vadisine dalış: her karede görüş %8 daralır. + kam.merkez_x = -0.743643887 + kam.merkez_y = 0.131825904 + konsol.yaz("\033[?25l\033[2J") # imleci gizle, ekranı temizle + kare = 0 + iken kare < 120: + t0 = zaman.milisaniye() + resim = kare_ciz(kume, kam, SUTUN, SATIR) + konsol.yaz("\033[H" + baslik(kume, kam, zaman.milisaniye() - t0) + "\033[K\n" + resim) + kam.genislik = kam.genislik * 0.92 + kam.sinir = kam.sinir + 5 + uyu(40) # hesap ~3 ms sürüyor; göz için yavaşlat + kare = kare + 1 + konsol.yaz("\033[?25h") +degilse: + t0 = zaman.milisaniye() + resim = kare_ciz(kume, kam, SUTUN, SATIR) + yazdir(baslik(kume, kam, zaman.milisaniye() - t0)) + konsol.yaz(resim) diff --git a/konsol/sudoku/README.md b/konsol/sudoku/README.md new file mode 100644 index 0000000..657a617 --- /dev/null +++ b/konsol/sudoku/README.md @@ -0,0 +1,39 @@ +# Sudoku — geri izlemeli çözücü, canlı animasyonla + +![Sudoku](../../medya/sudoku.png) + +Boş hücreye 1–9 arasındaki ilk uygun rakamı koyar ve ilerler. Çıkmaza +girerse bir önceki hücreye döner (**geri izleme**) ve oradaki bir sonraki +rakamı dener. Her adım ekranda görünür: + +* **beyaz** — bulmacada verilen rakam +* **camgöbeği** — çözücünün koyduğu, henüz kesinleşmemiş rakam +* **sarı** — şu an denenen hücre · **kırmızı ✗** — geri alınan hücre +* **yeşil** — çözüm bulunduktan sonra kesinleşen rakamlar + +```bash +kabalang calistir sudoku.kbl # animasyonlu (~10 sn) +kabalang calistir sudoku.kbl -- --hizli # yalnız sonuç +kabalang calistir sudoku.kbl -- 3000000000050090002005040000200007001600000587043106000008901000000670800000054 37 --hizli +``` + +Kendi bulmacanızı 81 haneli bir dizi olarak verebilirsiniz (`0` = boş hücre). + +## KabaLang'ın gösterdikleri + +| Özellik | Nerede | +|---|---| +| `sinif` + kurucu `kur` | `Cozucu` ızgarayı **kendi üzerinde** değiştirir (metotlar nesnenin kendisinde çalışır) | +| `sayilabilir` | Hücre durumu: `VERILEN · YERLESMIS · DENENIYOR · GERI_ALINDI · KESIN` | +| Tipli liste alanı | `izgara: liste = []`, `durum: liste = []` | +| `**` üs işleci | Doğrulamada her grubun `2¹ + … + 2⁹ = 1022` toplamı | +| f-string genişlik | `{self.deneme:5d}` sabit genişlikli sayaç | + +## Doğruluk + +Program çözümü yazmadan önce `gecerli_mi` ile denetler: her satır, sütun ve +3×3 kutu 1..9'u tam bir kez içermeli. Bu, çözücüden **bağımsız** bir +denetimdir — çözücü hatalı olsaydı ekranda `✓` görünmezdi. + +Ölçülen: varsayılan bulmaca 1.682 deneme / 1.631 geri alma; "dünyanın en zor +sudokusu" diye bilinen bulmacalardan biri 243.098 deneme, animasyonsuz ~55 ms. diff --git a/konsol/sudoku/sudoku.kbl b/konsol/sudoku/sudoku.kbl new file mode 100644 index 0000000..5356b02 --- /dev/null +++ b/konsol/sudoku/sudoku.kbl @@ -0,0 +1,197 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ SUDOKU — geri izlemeli çözücü, canlı animasyonla ║ +# ║ ║ +# ║ Boş hücreye 1..9 arası ilk uygun rakamı koyar ve ilerler. ║ +# ║ Çıkmaza girerse bir önceki hücreye döner (geri izleme) ve ║ +# ║ oradaki bir sonraki rakamı dener. Her adım ekranda görünür: ║ +# ║ sarı = şu an denenen · kırmızı = geri alınan · yeşil = çözüm ║ +# ║ ║ +# ║ kabalang calistir sudoku.kbl → animasyonlu ║ +# ║ kabalang calistir sudoku.kbl --hizli → yalnız sonuç ║ +# ╚══════════════════════════════════════════════════════════════════╝ +kullan kaba.sistem + +BULMACA = "030050040008010500460000012070502080000603000040109030250000098001020600080060020" + +sayilabilir Hucre: + VERILEN # bulmacada yazılı gelen rakam + YERLESMIS # çözücünün koyduğu, henüz kesin değil + DENENIYOR + GERI_ALINDI + KESIN + +# ── Renkler ────────────────────────────────────────────────────────── +fonksiyon boya(yazi: metin, rgb: metin) -> metin: + don "\033[38;2;" + rgb + "m" + yazi + "\033[0m" + +fonksiyon hucre_rengi(h: Hucre) -> metin: + eger h == Hucre.VERILEN: + don "235;240;255" + eger h == Hucre.DENENIYOR: + don "250;204;21" + eger h == Hucre.GERI_ALINDI: + don "248;113;113" + eger h == Hucre.KESIN: + don "74;222;128" + don "34;211;238" + +# ── Çözücü ─────────────────────────────────────────────────────────── +# Sınıf, ızgarayı kendi üzerinde değiştirir: metotlar nesnenin KENDİSİ +# üzerinde çalışır (işleve geçen liste ise kopyadır). +sinif Cozucu: + izgara: liste = [] + durum: liste = [] + deneme = 0 + geri_alma = 0 + canli = dogru + + fonksiyon kur(self, bulmaca: metin, canli: mantik): + i = 0 + iken i < 81: + rakam = sayi(bulmaca.dilim(i, 1)) + self.izgara.ekle(rakam) + eger rakam == 0: + self.durum.ekle(Hucre.YERLESMIS) + degilse: + self.durum.ekle(Hucre.VERILEN) + i = i + 1 + self.canli = canli + + # Aynı satırda, sütunda ya da 3×3 kutuda r rakamı var mı? + fonksiyon uygun_mu(self, konum: sayi, r: sayi) -> mantik: + satir = konum // 9 + sutun = konum % 9 + kutu = (satir // 3) * 27 + (sutun // 3) * 3 + i = 0 + iken i < 9: + eger self.izgara[satir * 9 + i] == r veya self.izgara[i * 9 + sutun] == r: + don yanlis + eger self.izgara[kutu + (i // 3) * 9 + i % 3] == r: + don yanlis + i = i + 1 + don dogru + + # Geri izleme, açık bir yığınla: `bos` boş hücrelerin listesi, `i` + # sıradaki boş hücre. İleri gidemeyince i bir geri çekilir. + fonksiyon coz(self) -> mantik: + bos = [0] # boş SAYI listesi: [] metin listesi olurdu + bos.sil(0) + k = 0 + iken k < 81: + eger self.izgara[k] == 0: + bos.ekle(k) + k = k + 1 + i = 0 + iken i >= 0 ve i < bos.uzunluk(): + konum = bos[i] + r = self.izgara[konum] + 1 + self.izgara[konum] = 0 + iken r <= 9 ve degil self.uygun_mu(konum, r): + r = r + 1 + eger r <= 9: + self.deneme = self.deneme + 1 + self.izgara[konum] = r + self.durum[konum] = Hucre.DENENIYOR + self.ciz(konum) + self.durum[konum] = Hucre.YERLESMIS + i = i + 1 + degilse: + self.geri_alma = self.geri_alma + 1 + self.durum[konum] = Hucre.GERI_ALINDI + self.ciz(konum) + self.durum[konum] = Hucre.YERLESMIS + i = i - 1 + don i == bos.uzunluk() + + fonksiyon tamamla(self): + i = 0 + iken i < 81: + eger self.durum[i] != Hucre.VERILEN: + self.durum[i] = Hucre.KESIN + i = i + 1 + + # ── Çizim ─────────────────────────────────────────────────────── + fonksiyon ekran(self) -> metin: + cizgi = boya(" ┣━━━━━━━━━╋━━━━━━━━━╋━━━━━━━━━┫", "70;80;110") + cikti = boya(" ┏━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓", "70;80;110") + "\n" + s = 0 + iken s < 9: + eger s == 3 veya s == 6: + cikti = cikti + cizgi + "\n" + satir = boya(" ┃", "70;80;110") + k = 0 + iken k < 9: + d = self.izgara[s * 9 + k] + h = self.durum[s * 9 + k] + yazi = " · " + eger d != 0: + yazi = f" {d} " + eger h == Hucre.GERI_ALINDI: + yazi = " ✗ " + satir = satir + boya(yazi, hucre_rengi(h)) + eger k % 3 == 2: + satir = satir + boya("┃", "70;80;110") + k = k + 1 + cikti = cikti + satir + "\n" + s = s + 1 + cikti = cikti + boya(" ┗━━━━━━━━━┻━━━━━━━━━┻━━━━━━━━━┛", "70;80;110") + "\n" + don cikti + + fonksiyon ciz(self, konum: sayi): + eger degil self.canli: + don + konsol.yaz("\033[H\n" + boya(" ◆ SUDOKU · geri izleme", "125;170;255") + "\n\n" + self.ekran()) + konsol.yaz(f"\n deneme {self.deneme:5d} geri alma {self.geri_alma:5d} hücre {konum // 9 + 1},{konum % 9 + 1} \n") + uyu(3) + +# Çözüm gerçekten geçerli mi? Her satır, sütun ve kutu 1..9'u tam bir kez içermeli. +fonksiyon gecerli_mi(izgara) -> mantik: + grup = 0 + iken grup < 9: + satir_toplam = 0 + sutun_toplam = 0 + kutu_toplam = 0 + i = 0 + iken i < 9: + satir_toplam = satir_toplam + 2 ** izgara[grup * 9 + i] + sutun_toplam = sutun_toplam + 2 ** izgara[i * 9 + grup] + kutu_toplam = kutu_toplam + 2 ** izgara[(grup // 3) * 27 + (grup % 3) * 3 + (i // 3) * 9 + i % 3] + i = i + 1 + # 2¹ + 2² + … + 2⁹ = 1022: yalnız 1..9'un her biri bir kez varsa tutar. + eger satir_toplam != 1022 veya sutun_toplam != 1022 veya kutu_toplam != 1022: + don yanlis + grup = grup + 1 + don dogru + +# ── Program ────────────────────────────────────────────────────────── +ks = KomutSatiri("sudoku", "Geri izlemeli sudoku çözücü") +ks.bayrak("hizli", "h", "Animasyonsuz, yalnız sonucu göster") +ks.konumsal("bulmaca", "81 haneli bulmaca (0 = boş hücre)") +c = ks.coz() +eger c.yardim_mi(): + c.yardim_yaz() + cikis(0) +eger c.hatali_mi(): + c.hata_yaz_ve_cik() + +bulmaca = c.konumsal("bulmaca").deger_veya(BULMACA) +eger bulmaca.uzunluk() != 81: + konsol.hata_yaz("bulmaca 81 haneli olmalı\n") + cikis(2) + +cozucu = Cozucu(bulmaca, degil c.bayrak("hizli")) +konsol.yaz("\033[2J\033[?25l") +t0 = zaman.milisaniye() +basardi = cozucu.coz() +sure = zaman.milisaniye() - t0 +cozucu.tamamla() + +konsol.yaz("\033[H\n" + boya(" ◆ SUDOKU · geri izleme", "125;170;255") + "\n\n" + cozucu.ekran()) +eger basardi ve gecerli_mi(cozucu.izgara): + ek = "" + eger cozucu.canli: + ek = " (animasyon dahil)" + yazdir(boya(f"\n ✓ çözüldü — {cozucu.deneme} deneme, {cozucu.geri_alma} geri alma, {sure} ms{ek}", "74;222;128") + "\033[K") +degilse: + yazdir(boya("\n ✗ bu bulmacanın çözümü yok", "248;113;113") + "\033[K") +konsol.yaz("\033[?25h") diff --git a/medya/anket-oy.png b/medya/anket-oy.png new file mode 100644 index 0000000..afd8f35 Binary files /dev/null and b/medya/anket-oy.png differ diff --git a/medya/anket.png b/medya/anket.png new file mode 100644 index 0000000..525d3ba Binary files /dev/null and b/medya/anket.png differ diff --git a/medya/fatura-pdf.png b/medya/fatura-pdf.png new file mode 100644 index 0000000..94b7208 Binary files /dev/null and b/medya/fatura-pdf.png differ diff --git a/medya/fatura.png b/medya/fatura.png new file mode 100644 index 0000000..7f6fc6d Binary files /dev/null and b/medya/fatura.png differ diff --git a/medya/fraktal-julia.png b/medya/fraktal-julia.png new file mode 100644 index 0000000..3e7ba1a Binary files /dev/null and b/medya/fraktal-julia.png differ diff --git a/medya/fraktal.png b/medya/fraktal.png new file mode 100644 index 0000000..416c776 Binary files /dev/null and b/medya/fraktal.png differ diff --git a/medya/hesap-makinesi.png b/medya/hesap-makinesi.png new file mode 100644 index 0000000..274a24e Binary files /dev/null and b/medya/hesap-makinesi.png differ diff --git a/medya/kcode.png b/medya/kcode.png new file mode 100644 index 0000000..64c369b Binary files /dev/null and b/medya/kcode.png differ diff --git a/medya/kisalt-istatistik.png b/medya/kisalt-istatistik.png new file mode 100644 index 0000000..4c06da1 Binary files /dev/null and b/medya/kisalt-istatistik.png differ diff --git a/medya/kisalt-mobil.png b/medya/kisalt-mobil.png new file mode 100644 index 0000000..286fba0 Binary files /dev/null and b/medya/kisalt-mobil.png differ diff --git a/medya/kisalt.png b/medya/kisalt.png new file mode 100644 index 0000000..de8e598 Binary files /dev/null and b/medya/kisalt.png differ diff --git a/medya/odak.png b/medya/odak.png new file mode 100644 index 0000000..b53e58c Binary files /dev/null and b/medya/odak.png differ diff --git a/medya/pano-mobil.png b/medya/pano-mobil.png new file mode 100644 index 0000000..7c57102 Binary files /dev/null and b/medya/pano-mobil.png differ diff --git a/medya/pano.png b/medya/pano.png new file mode 100644 index 0000000..aab202d Binary files /dev/null and b/medya/pano.png differ diff --git a/medya/parola-atolyesi-ilk.png b/medya/parola-atolyesi-ilk.png new file mode 100644 index 0000000..4d3fa4c Binary files /dev/null and b/medya/parola-atolyesi-ilk.png differ diff --git a/medya/parola-atolyesi.png b/medya/parola-atolyesi.png new file mode 100644 index 0000000..10f9295 Binary files /dev/null and b/medya/parola-atolyesi.png differ diff --git a/medya/sudoku.png b/medya/sudoku.png new file mode 100644 index 0000000..4e31c9e Binary files /dev/null and b/medya/sudoku.png differ diff --git a/web/anket/README.md b/web/anket/README.md new file mode 100644 index 0000000..4825eed --- /dev/null +++ b/web/anket/README.md @@ -0,0 +1,35 @@ +# Anket — canlı oylama + +![Anket](../../medya/anket.png) + +Tek soruluk canlı anket. Oylar SQLite'ta tutulur, aynı adresten ikinci oy **veritabanı +kısıtıyla** engellenir, sonuç grafiği her istekte sunucuda KabaLang ile **SVG** olarak çizilir. + +```bash +kabalang calistir web/anket/anket.kbl +# → http://127.0.0.1:8473 +``` + +## Neyi gösteriyor + +| Konu | Nerede | +|---|---| +| `yapi Secenek` + tipli alanlar, yapı listesi döndüren fonksiyon (`-> liste`) | `secenekler` | +| Gövdesiz form: oy kimliği yolda (`POST /oy/{id}`) | `oy_ver` | +| POST → **303** → GET deseni (sayfa yenilenince oy tekrar gitmez) | `oy_ver` | +| Aynı grafiği iki genişlikte üretmek (masaüstü / telefon) | `grafik_svg(liste, isaretli, gen)` | +| Doğru içerik tipiyle tek başına SVG: `.tip("image/svg+xml")` | `/sonuc.svg` | +| JSON'u SQLite'ın `json_object` işlevine kurdurmak | `/api/sonuclar` | + +![Oy ekranı](../../medya/anket-oy.png) + +## Güvenlik + +- **SQL enjeksiyonu:** seçenek kimliği ve istemci adresi sorguya `?` ile bağlanır. + `/oy/1 OR 1=1` var olmayan bir seçenektir → 404. +- **Çift oy:** `oy.adres` sütunu `UNIQUE`; denetim ile kayıt tek iş parçacıklı sunucuda sırayla + koşar, yarış koşulu yoktur. +- Sunucu yalnız `127.0.0.1`'i dinler. (Yerelde her istemci aynı adres olduğu için tarayıcı + bir kez oy verebilir — anketi sıfırlamak için `anket.db`'yi silin.) + +Veritabanı yolu `ANKET_DB` ile değiştirilebilir (varsayılan `anket.db`). diff --git a/web/anket/anket.kbl b/web/anket/anket.kbl new file mode 100644 index 0000000..55cc0b7 --- /dev/null +++ b/web/anket/anket.kbl @@ -0,0 +1,197 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ ANKET — KabaLang ile canlı anket ║ +# ║ ║ +# ║ · Oy: POST /oy/{id} — gövdesiz form, kimlik yolda ║ +# ║ · Aynı adresten ikinci oy veritabanı kısıtıyla engellenir ║ +# ║ · Sonuç grafiği sunucuda KabaLang ile SVG olarak çizilir ║ +# ║ · /sonuc.svg tek başına gömülebilir · /api/sonuclar JSON ║ +# ╚══════════════════════════════════════════════════════════════════╝ +# +# Çalıştır: kabalang calistir web/anket/anket.kbl +# Aç: http://127.0.0.1:8473 + +kullan kaba.web +kullan kaba.json +kullan kaba.veritabani +kullan kaba.sistem + +SORU = "Bir sonraki KabaLang örneği ne olsun?" +PORT = 8473 + +db = cevre.al("ANKET_DB") +eger db == "": + db = "anket.db" + +yapi Secenek: + kimlik: sayi = 0 + ad: metin = "" + renk: metin = "" + oy: sayi = 0 + +# ── Veritabanı ────────────────────────────────────────────────────── + +fonksiyon hazirla(db): + # `adres UNIQUE`: aynı istemciden ikinci oy veritabanı düzeyinde reddedilir. + veritabani.sqlite(db, "CREATE TABLE IF NOT EXISTS secenek(id INTEGER PRIMARY KEY, ad TEXT, renk TEXT); CREATE TABLE IF NOT EXISTS oy(secenek INTEGER, adres TEXT UNIQUE, zaman TEXT);") + eger veritabani.sqlite(db, "SELECT count(*) FROM secenek") != "0": + don 0 + adlar = ["2B oyun", "Yapay zekâ asistanı", "Veri panosu", "Sohbet uygulaması", "Sistem servisi"] + renkler = ["#377ef4", "#22d3ee", "#a78bfa", "#34d399", "#fbbf24"] + ornek_oy = [18, 27, 12, 21, 7] + dongu i icinde 0..adlar.uzunluk() - 1: + veritabani.sqlite(db, "INSERT INTO secenek(id, ad, renk) VALUES (?, ?, ?)", [metin(i + 1), adlar[i], renkler[i]]) + # Tanıtım için önceden verilmiş oylar (adres alanı uydurma ve benzersiz). + dongu n icinde 1..ornek_oy[i]: + veritabani.sqlite(db, "INSERT INTO oy(secenek, adres, zaman) VALUES (?, ?, datetime('now', 'localtime'))", [metin(i + 1), "ornek-" + metin(i) + "-" + metin(n)]) + don 1 + +fonksiyon secenekler(db) -> liste: + sonuc = [Secenek()] + sonuc.sil(0) + ham = veritabani.sqlite(db, "SELECT s.id, s.renk, (SELECT count(*) FROM oy WHERE oy.secenek = s.id), s.ad FROM secenek s ORDER BY s.id") + dongu satir icinde ham.bol("\n"): + p = satir.bol("|") + sonuc.ekle(Secenek(sayi(p[0]), p.dilim(3, p.uzunluk() - 3).birlestir("|"), p[1], sayi(p[2]))) + don sonuc + +fonksiyon toplam_oy(liste): + t = 0 + dongu s icinde liste: + t = t + s.oy + don t + +fonksiyon verdigi_oy(db, adres): + don veritabani.sqlite(db, "SELECT secenek FROM oy WHERE adres = ?", [adres]) + +# ── SVG grafik: tamamen KabaLang ile ──────────────────────────────── + +# `gen`: çizim genişliği. Sayfa aynı grafiği iki genişlikte üretir +# (masaüstü / telefon) ki yazılar küçülmesin. +fonksiyon grafik_svg(liste, isaretli, gen): + toplam = toplam_oy(liste) + eger toplam == 0: + toplam = 1 + # Çubuk boyu en çok oy alana göre ölçeklenir: lider tam dolar. + en_cok = 1 + dongu sec icinde liste: + eger sec.oy > en_cok: + en_cok = sec.oy + satir_yuk = 64 + yuk = liste.uzunluk() * satir_yuk + 10 + s = "" + i = 0 + dongu sec icinde liste: + y = i * satir_yuk + 8 + yuzde = sec.oy * 100 // toplam + cubuk = (gen - 120) * sec.oy // en_cok + eger cubuk < 6: + cubuk = 6 + kalinlik = "500" + eger metin(sec.kimlik) == isaretli: + kalinlik = "800" + # Ad metni kullanıcıdan gelmiyor ama yine de kaçırılır: alışkanlık. + s = s + "" + web.kacir(sec.ad) + "" + s = s + "" + s = s + "" + s = s + "%" + metin(yuzde) + "" + s = s + "" + metin(sec.oy) + "" + i = i + 1 + don s + "" + +# ── Sayfa iskeleti ────────────────────────────────────────────────── + +STIL = """ +:root { --zemin:#0a0d15; --yuzey:#121826; --yuzey2:#182033; --cizgi:#232c42; --metin:#e6ebf5; + --soluk:#8a94ab; --mavi:#377ef4; --cam:#22d3ee; } +* { box-sizing: border-box; } +body { margin:0; min-height:100vh; color:var(--metin); font:15px/1.55 Inter, system-ui, -apple-system, "Segoe UI", sans-serif; + background: radial-gradient(800px 460px at 20% -10%, #377ef430, transparent 60%), + radial-gradient(700px 420px at 90% 110%, #a78bfa22, transparent 60%), var(--zemin); + display:grid; place-items:center; padding: 32px 16px; } +.kart { width: 100%; max-width: 820px; background: #121826e6; border:1px solid var(--cizgi); border-radius: 24px; + padding: 34px 38px; box-shadow: 0 30px 80px #0009; } +.ust { display:flex; align-items:center; gap:10px; color: var(--soluk); font-size: 13px; margin-bottom: 18px; } +.canli { display:inline-flex; align-items:center; gap:7px; color:#34d399; margin-left:auto; } +.canli::before { content:''; width:8px; height:8px; border-radius:50%; background:#34d399; box-shadow:0 0 0 4px #34d39928; } +h1 { font-size: clamp(24px, 4vw, 34px); line-height: 1.15; letter-spacing: -.02em; margin: 0 0 24px; } +.secenekler { display:grid; gap: 10px; } +.secenekler form { margin:0; } +.secenekler button { width:100%; display:flex; align-items:center; gap:14px; text-align:left; font:inherit; font-size:16px; + color: var(--metin); background: var(--yuzey2); border:1px solid var(--cizgi); border-radius: 14px; + padding: 15px 18px; cursor:pointer; transition: border-color .15s, transform .15s; } +.secenekler button:hover { border-color: var(--mavi); transform: translateX(4px); } +.top { width: 14px; height: 14px; border-radius: 50%; flex: none; } +.alt { display:flex; justify-content: space-between; color: var(--soluk); font-size: 13px; margin-top: 22px; flex-wrap: wrap; gap: 8px; } +.alt a { color: var(--cam); text-decoration:none; } +.bilgi { background: #377ef414; border:1px solid #377ef444; color:#bcd3ff; border-radius: 12px; padding: 10px 14px; margin-bottom: 20px; font-size: 14px; } +svg { width:100%; height:auto; display:block; } +.dar { display: none; } +@media (max-width: 600px) { .kart { padding: 24px 18px; border-radius: 20px; } .genis { display: none; } .dar { display: block; } } +""" + +fonksiyon sayfa(govde, yenile): + ek = "" + eger yenile: + # "Canlı": sonuç sayfası 5 saniyede bir kendini tazeler. + ek = "" + don "" + ek + "Anket · KabaLang
" + govde + "
" + +fonksiyon ust_bant(toplam): + don "
KabaLang Anket · " + metin(toplam) + " oycanlı
" + +# ── Uçlar ─────────────────────────────────────────────────────────── + +hazirla(db) + +fonksiyon sonuc_sayfasi(istek): + liste = secenekler(db) + benim = verdigi_oy(db, istek.adres()) + g = ust_bant(toplam_oy(liste)) + "

" + web.kacir(SORU) + "

" + eger istek.sorgu("zaten") == "1": + g = g + "
Bu adresten zaten oy verilmiş — ikinci oy sayılmadı.
" + degilse eger benim != "": + g = g + "
✓ Oyunuz kaydedildi. Sonuçlar 5 saniyede bir tazelenir.
" + g = g + "
" + grafik_svg(liste, benim, 760) + "
" + g = g + "
" + grafik_svg(liste, benim, 380) + "
" + g = g + "
Grafik sunucuda KabaLang ile çizildi · sonuc.svg · JSON← ankete dön
" + don web.html(sayfa(g, dogru)) + +fonksiyon ana_sayfa(istek): + # Daha önce oy verdiyse doğrudan sonuçları göster. + eger verdigi_oy(db, istek.adres()) != "": + don sonuc_sayfasi(istek) + liste = secenekler(db) + g = ust_bant(toplam_oy(liste)) + "

" + web.kacir(SORU) + "

" + dongu s icinde liste: + g = g + "
" + g = g + "
Her adres bir kez oy verebilir.sonuçları gör →
" + don web.html(sayfa(g, yanlis)) + +fonksiyon oy_ver(istek): + kimlik = istek.parametre("id") + # Kimlik gerçekten var olan bir seçenek mi? (parametre bağlamalı) + eger veritabani.sqlite(db, "SELECT count(*) FROM secenek WHERE id = ?", [kimlik]) != "1": + don web.html(sayfa("

Böyle bir seçenek yok

", yanlis), 404) + adres = istek.adres() + eger verdigi_oy(db, adres) != "": + don web.yanit("", 303).baslik("Location", "/sonuc?zaten=1") + veritabani.sqlite(db, "INSERT OR IGNORE INTO oy(secenek, adres, zaman) VALUES (?, ?, datetime('now', 'localtime'))", [kimlik, adres]) + # POST → 303 → GET: sayfa yenilenince oy ikinci kez gönderilmez. + don web.yanit("", 303).baslik("Location", "/sonuc") + +fonksiyon sonuc_svg(istek): + don web.yanit(grafik_svg(secenekler(db), verdigi_oy(db, istek.adres()), 760)).tip("image/svg+xml; charset=utf-8") + +fonksiyon sonuc_json(_istek): + don web.json(veritabani.sqlite(db, "SELECT json_object('soru', ?, 'toplam', (SELECT count(*) FROM oy), 'secenekler', (SELECT json_group_array(json_object('id', s.id, 'ad', s.ad, 'oy', (SELECT count(*) FROM oy WHERE oy.secenek = s.id))) FROM secenek s))", [SORU])) + +# Tek iş parçacığı: "oy verdi mi?" denetimi ile kayıt arasında yarış olmasın. +sunucu = web.sunucu(PORT, "127.0.0.1", 1) +sunucu.al("/", ana_sayfa) +sunucu.al("/sonuc", sonuc_sayfasi) +sunucu.al("/sonuc.svg", sonuc_svg) +sunucu.al("/api/sonuclar", sonuc_json) +sunucu.gonder("/oy/{id}", oy_ver) + +yazdir("🗳 Anket hazır — http://127.0.0.1:" + metin(PORT)) +sunucu.basla() diff --git a/web/kisalt/README.md b/web/kisalt/README.md new file mode 100644 index 0000000..1825c43 --- /dev/null +++ b/web/kisalt/README.md @@ -0,0 +1,38 @@ +# Kısalt — bağlantı kısaltıcı + +![Kısalt](../../medya/kisalt.png) + +Uzun bir adres verin, KabaLang ona 6 harflik bir kod versin; `/k/{kod}` gerçek bir +**HTTP 302** ile hedefe yönlendirir ve her tıklamayı sayar. Sayfaların tamamı sunucuda, +KabaLang metin işlemleriyle üretilir — şablon motoru yok. + +```bash +kabalang calistir web/kisalt/kisalt.kbl +# → http://127.0.0.1:8472 +``` + +## Neyi gösteriyor + +| Konu | Nerede | +|---|---| +| Rastgele kısa kod + çakışma döngüsü (`iken ... != "0"`) | `yeni_kod`, `kaydet` | +| Gerçek yönlendirme: `web.yanit("", 302).baslik("Location", adres)` | `yonlendir` | +| Sunucuda çizilen **SVG çubuk grafik** (son 7 gün) | `yedi_gun_svg` | +| Üç tırnaklı metin ile tek yerde tutulan stil | `STIL = """ ... """` | +| Özel 404 sayfası: `sunucu.bulunamadi(...)` | sonda | + +![İstatistik](../../medya/kisalt-istatistik.png) + +## Güvenlik + +- **SQL enjeksiyonu:** bütün sorgular parametre bağlamalıdır (`?`); kullanıcı verisi asla + sorgu metnine eklenmez. +- **Açık yönlendirme / betik bağlantısı:** yalnız `http://` ve `https://` adresleri kabul edilir; + `javascript:` ya da `file:` ile başlayan bir "bağlantı" 422 ile reddedilir. +- **XSS:** hedef adres sayfaya yalnızca **metin** olarak, `web.kacir` ile kaçırılarak basılır; + `href` içine giren tek şey bizim ürettiğimiz, yalnız harf-rakamdan oluşan kısa koddur. + +Veritabanı yolu `KISALT_DB` ile değiştirilebilir (varsayılan `kisalt.db`). İlk açılışta +tanıtım için birkaç bağlantı ve geçmiş tıklama oluşturulur. + +![Mobil](../../medya/kisalt-mobil.png) diff --git a/web/kisalt/kisalt.kbl b/web/kisalt/kisalt.kbl new file mode 100644 index 0000000..81ef0eb --- /dev/null +++ b/web/kisalt/kisalt.kbl @@ -0,0 +1,285 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ KISALT — KabaLang ile bağlantı kısaltıcı ║ +# ║ ║ +# ║ · Sayfalar sunucuda KabaLang ile üretilir (şablon motoru yok) ║ +# ║ · /k/{kod} → gerçek HTTP 302 yönlendirmesi + tıklama kaydı ║ +# ║ · İstatistik sayfasındaki grafik: sunucuda üretilen SVG ║ +# ╚══════════════════════════════════════════════════════════════════╝ +# +# Çalıştır: kabalang calistir web/kisalt/kisalt.kbl +# Aç: http://127.0.0.1:8472 + +kullan kaba.web +kullan kaba.json +kullan kaba.veritabani +kullan kaba.sistem + +# Karıştırılabilecek harfler (0/o, 1/l/i) bilerek yok. +ALFABE = "abcdefghjkmnpqrstuvwxyz23456789" +KOD_UZUNLUGU = 6 +PORT = 8472 + +db = cevre.al("KISALT_DB") +eger db == "": + db = "kisalt.db" + +# ── Stil: tüm sayfalar aynı görünümü paylaşır ─────────────────────── + +STIL = """ +:root { --zemin:#0a0d15; --yuzey:#121826; --yuzey2:#182033; --cizgi:#232c42; + --metin:#e6ebf5; --soluk:#8a94ab; --mavi:#377ef4; --cam:#22d3ee; --yesil:#34d399; } +* { box-sizing: border-box; } +body { margin:0; min-height:100vh; color:var(--metin); + font:15px/1.55 Inter, system-ui, -apple-system, "Segoe UI", sans-serif; + background: radial-gradient(900px 480px at 50% -12%, #377ef433, transparent 65%), + radial-gradient(600px 360px at 95% 10%, #22d3ee18, transparent 60%), var(--zemin); } +a { color: var(--cam); text-decoration: none; } +a:hover { text-decoration: underline; } +.kap { max-width: 980px; margin: 0 auto; padding: 0 24px 56px; } +nav { display:flex; align-items:center; gap:12px; padding: 22px 0; } +.logo { width:36px; height:36px; border-radius:11px; display:grid; place-items:center; font-weight:800; + background:linear-gradient(135deg,var(--mavi),var(--cam)); box-shadow:0 6px 22px #377ef455; } +nav b { font-size: 18px; letter-spacing: -.01em; } +nav span { margin-left:auto; color:var(--soluk); font-size:13px; } +.kahraman { text-align:center; padding: 46px 0 30px; } +.kahraman h1 { font-size: clamp(30px, 5vw, 48px); line-height:1.1; margin:0 0 12px; letter-spacing:-.03em; } +.kahraman h1 em { font-style: normal; background: linear-gradient(90deg, var(--mavi), var(--cam)); + -webkit-background-clip: text; background-clip: text; color: transparent; } +.kahraman p { color: var(--soluk); margin: 0 0 28px; } +form { display:flex; gap:10px; max-width: 720px; margin: 0 auto; background: var(--yuzey); + border:1px solid var(--cizgi); border-radius: 16px; padding: 8px; box-shadow: 0 20px 60px #0007; } +input { flex:1; min-width:0; font:inherit; color:var(--metin); background:transparent; border:0; padding: 12px 14px; outline:none; } +button { font:inherit; font-weight:600; color:#fff; border:0; border-radius: 11px; padding: 12px 22px; cursor:pointer; + background: linear-gradient(135deg, var(--mavi), #2563eb); box-shadow: 0 6px 18px #377ef450; } +#sonuc { max-width:720px; margin: 14px auto 0; min-height: 24px; font-size: 14px; color: var(--soluk); } +#sonuc .kisa { color: var(--yesil); font-weight: 600; font-family: ui-monospace, monospace; font-size: 15px; } +.kutular { display:grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 34px 0 18px; } +.kutu { background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; padding: 16px 18px; } +.kutu small { color: var(--soluk); font-size: 12px; text-transform: uppercase; letter-spacing: .07em; } +.kutu div { font-size: 26px; font-weight: 700; letter-spacing: -.02em; } +table { width:100%; border-collapse: collapse; background: var(--yuzey); border:1px solid var(--cizgi); + border-radius: 16px; overflow:hidden; } +th, td { text-align:left; padding: 12px 16px; border-bottom: 1px solid var(--cizgi); font-size: 14px; } +th { color: var(--soluk); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .07em; background: var(--yuzey2); } +tr:last-child td { border-bottom: 0; } +td.hedef { color: var(--soluk); max-width: 360px; overflow:hidden; text-overflow: ellipsis; white-space: nowrap; } +td.kod { font-family: ui-monospace, monospace; } +.sayac { font-weight: 700; color: var(--metin); } +.grafik { background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; padding: 18px; margin-top: 14px; } +.grafik svg { width: 100%; height: auto; display:block; } +.adres { word-break: break-all; color: var(--soluk); } +footer { text-align:center; color: var(--soluk); font-size: 12px; margin-top: 30px; } +@media (max-width: 680px) { + .kap { padding: 0 16px 40px; } + form { flex-direction: column; } + .kutular { grid-template-columns: 1fr 1fr; } + .kutular .kutu:last-child { grid-column: 1 / -1; } + td.hedef, th.hedef { display: none; } +} +""" + +fonksiyon sayfa(baslik, govde): + don "" + web.kacir(baslik) + "
" + govde + "
Bu sayfa sunucuda KabaLang ile üretildi.
" + +# ── Veritabanı ────────────────────────────────────────────────────── + +fonksiyon hazirla(db): + veritabani.sqlite(db, "CREATE TABLE IF NOT EXISTS baglanti(kod TEXT PRIMARY KEY, adres TEXT NOT NULL, olusturma TEXT, tiklama INTEGER DEFAULT 0); CREATE TABLE IF NOT EXISTS tiklama(kod TEXT, zaman TEXT);") + +# Satırları `|` ile ayrılmış sütunlara böler. Adres son sütundur ve +# kendi içinde `|` taşıyabilir → kalan parçalar geri birleştirilir. +fonksiyon satirlar(ham): + sonuc = [] + eger ham == "": + don sonuc + dongu s icinde ham.bol("\n"): + sonuc.ekle(s) + don sonuc + +fonksiyon son_sutun(parcalar, bas): + don parcalar.dilim(bas, parcalar.uzunluk() - bas).birlestir("|") + +# ── Kısa kod ──────────────────────────────────────────────────────── + +fonksiyon yeni_kod(): + kod = "" + dongu _ icinde 1..KOD_UZUNLUGU: + kod = kod + ALFABE.dilim(rastgele.sayi(0, ALFABE.uzunluk() - 1), 1) + don kod + +fonksiyon kod_gecerli(k): + eger k.uzunluk() != KOD_UZUNLUGU: + don yanlis + dongu i icinde 0..k.uzunluk() - 1: + eger degil ALFABE.icerir(k.dilim(i, 1)): + don yanlis + don dogru + +# Hedef adres yalnız http(s) olabilir: `javascript:` ya da `file:` ile +# başlayan bir "bağlantı" kısaltıcıyı saldırı aracına çevirirdi. +fonksiyon adres_dogrula(a): + eger a.uzunluk() > 2000: + don "adres çok uzun" + eger degil (a.ile_baslar("https://") veya a.ile_baslar("http://")): + don "adres http:// ya da https:// ile başlamalı" + eger a.icerir(" ") veya a.icerir("\n") veya a.icerir("\r") veya a.icerir("\t"): + don "adres boşluk içeremez" + eger a.uzunluk() < 12: + don "adres eksik görünüyor" + don "" + +fonksiyon kaydet(db, adres): + kod = yeni_kod() + # Çakışma olasılığı çok düşük ama sıfır değil: boş kod bulana dek dene. + iken veritabani.sqlite(db, "SELECT count(*) FROM baglanti WHERE kod = ?", [kod]) != "0": + kod = yeni_kod() + veritabani.sqlite(db, "INSERT INTO baglanti(kod, adres, olusturma) VALUES (?, ?, ?)", [kod, adres, zaman.simdi()]) + don kod + +# Tanıtım verisi: ilk açılışta birkaç bağlantı ve geçmiş tıklamalar. +fonksiyon ornek_veri(db): + eger veritabani.sqlite(db, "SELECT count(*) FROM baglanti") != "0": + don 0 + adresler = ["https://kabaforce.com", "https://dev.kabaforce.com/kabalang/", "https://forge.kabaforce.com/KabaLang/ornekler", "https://maps.kabaforce.com/?q=Galata+Kulesi", "https://dev.kabaforce.com/kabalang/kilavuz/12-web-uygulamasi"] + agirlik = [7, 11, 5, 3, 2] + dongu i icinde 0..adresler.uzunluk() - 1: + kod = kaydet(db, adresler[i]) + veritabani.sqlite(db, "UPDATE baglanti SET olusturma = datetime('now', 'localtime', ?) WHERE kod = ?", ["-" + metin(7 + i) + " days", kod]) + # Son yedi güne yayılmış tıklamalar. + dongu gun icinde 0..6: + adet = rastgele.sayi(0, agirlik[i]) + dongu _ icinde 1..adet: + veritabani.sqlite(db, "INSERT INTO tiklama(kod, zaman) VALUES (?, datetime('now', 'localtime', ?))", [kod, "-" + metin(gun) + " days"]) + veritabani.sqlite(db, "UPDATE baglanti SET tiklama = (SELECT count(*) FROM tiklama WHERE tiklama.kod = baglanti.kod) WHERE kod = ?", [kod]) + don 1 + +hazirla(db) +ornek_veri(db) + +# ── Sayfalar ──────────────────────────────────────────────────────── + +fonksiyon ana_sayfa(_istek): + toplam = veritabani.sqlite(db, "SELECT count(*) FROM baglanti") + tik = veritabani.sqlite(db, "SELECT coalesce(sum(tiklama), 0) FROM baglanti") + bugun = veritabani.sqlite(db, "SELECT count(*) FROM tiklama WHERE date(zaman) = date('now', 'localtime')") + + g = "

Uzun bağlantılar,
kısa ve ölçülebilir.

" + g = g + "

Bir adres yapıştırın; KabaLang ona kısa bir kod versin ve her tıklamayı saysın.

" + g = g + "
" + g = g + "
" + + g = g + "
" + g = g + "
Bağlantı
" + toplam + "
" + g = g + "
Toplam tıklama
" + tik + "
" + g = g + "
Bugün
" + bugun + "
" + g = g + "
" + + g = g + "" + dongu s icinde satirlar(veritabani.sqlite(db, "SELECT kod, tiklama, adres FROM baglanti ORDER BY olusturma DESC LIMIT 12")): + p = s.bol("|") + kod = p[0] + # Kod yalnız ALFABE harflerinden oluşur → href içinde güvenli. + # Hedef adres kullanıcıdan geldi → yalnız METİN olarak, kaçırılarak basılır. + g = g + "" + g = g + "" + g = g + "" + g = g + "" + g = g + "
Kısa kodHedefTıklama
/k/" + kod + "" + web.kacir(son_sutun(p, 2)) + "" + p[1] + "istatistik →
" + + # Küçük bir istemci betiği: formu JSON API'ye gönderir. + g = g + "" + don web.html(sayfa("Kısalt · KabaLang", g)) + +fonksiyon kisalt_api(istek): + govde = istek.govde() + eger degil json.gecerli_mi(govde): + don web.json(json.uret({"hata": "gövde geçerli JSON değil"}), 400) + adres = json.al(govde, "adres").kirp() + sorun = adres_dogrula(adres) + eger sorun != "": + don web.json(json.uret({"hata": sorun}), 422) + kod = kaydet(db, adres) + don web.json(json.uret({"kod": kod, "kisa": "/k/" + kod, "adres": adres}), 201) + +fonksiyon bulunamadi_sayfasi(): + g = "

Bu kod bulunamadı

Bağlantı silinmiş ya da hiç var olmamış olabilir.

← ana sayfa

" + don web.html(sayfa("Bulunamadı · Kısalt", g), 404) + +fonksiyon yonlendir(istek): + kod = istek.parametre("kod") + eger degil kod_gecerli(kod): + don bulunamadi_sayfasi() + adres = veritabani.sqlite(db, "SELECT adres FROM baglanti WHERE kod = ?", [kod]) + eger adres == "": + don bulunamadi_sayfasi() + veritabani.sqlite(db, "INSERT INTO tiklama(kod, zaman) VALUES (?, datetime('now', 'localtime'))", [kod]) + veritabani.sqlite(db, "UPDATE baglanti SET tiklama = tiklama + 1 WHERE kod = ?", [kod]) + # Gerçek HTTP yönlendirmesi: 302 + Location başlığı. + don web.yanit("", 302).baslik("Location", adres) + +# Son 7 günün tıklamalarını SVG çubuk grafiğe çevirir — tamamen sunucuda. +fonksiyon yedi_gun_svg(db, kod): + gunler = [] + adetler = [] + en_cok = 1 + dongu i icinde 0..6: + geri = 6 - i + gun = veritabani.sqlite(db, "SELECT strftime('%d.%m', 'now', 'localtime', ?)", ["-" + metin(geri) + " days"]) + adet = sayi(veritabani.sqlite(db, "SELECT count(*) FROM tiklama WHERE kod = ? AND date(zaman) = date('now', 'localtime', ?)", [kod, "-" + metin(geri) + " days"])) + gunler.ekle(gun) + adetler.ekle(adet) + eger adet > en_cok: + en_cok = adet + + gen = 700 + yuk = 220 + cubuk = 64 + aralik = 36 + svg = "" + svg = svg + "" + dongu i icinde 0..6: + h = adetler[i] * (yuk - 30) // en_cok + eger h < 3: + h = 3 + x = 30 + i * (cubuk + aralik) + y = yuk - h + svg = svg + "" + svg = svg + "" + metin(adetler[i]) + "" + svg = svg + "" + gunler[i] + "" + don svg + "" + +fonksiyon istatistik(istek): + kod = istek.parametre("kod") + eger degil kod_gecerli(kod): + don bulunamadi_sayfasi() + satir = veritabani.sqlite(db, "SELECT tiklama, olusturma, adres FROM baglanti WHERE kod = ?", [kod]) + eger satir == "": + don bulunamadi_sayfasi() + p = satir.bol("|") + son = veritabani.sqlite(db, "SELECT coalesce(max(zaman), '—') FROM tiklama WHERE kod = ?", [kod]) + + g = "

/k/" + kod + "

" + g = g + "

→ " + web.kacir(son_sutun(p, 2)) + "

" + g = g + "
" + g = g + "
Toplam tıklama
" + p[0] + "
" + g = g + "
Oluşturuldu
" + p[1] + "
" + g = g + "
Son tıklama
" + son + "
" + g = g + "
" + g = g + "
Son 7 gün" + yedi_gun_svg(db, kod) + "
" + g = g + "

← tüm bağlantılar

" + don web.html(sayfa("/k/" + kod + " · istatistik", g)) + +# Tek iş parçacığı: tıklama sayacı yarış koşuluna girmesin. +sunucu = web.sunucu(PORT, "127.0.0.1", 1) +sunucu.govde_siniri(8192) +sunucu.al("/", ana_sayfa) +sunucu.gonder("/api/kisalt", kisalt_api) +sunucu.al("/k/{kod}", yonlendir) +sunucu.al("/istatistik/{kod}", istatistik) +sunucu.bulunamadi((_istek) => bulunamadi_sayfasi()) + +yazdir("🔗 Kısalt hazır — http://127.0.0.1:" + metin(PORT)) +sunucu.basla() diff --git a/web/pano/README.md b/web/pano/README.md new file mode 100644 index 0000000..5e23d67 --- /dev/null +++ b/web/pano/README.md @@ -0,0 +1,45 @@ +# Pano — Kanban görev panosu + +![Pano](../../medya/pano.png) + +Sunucusu **KabaLang** ile yazılmış bir Kanban panosu: görevler *Yapılacak · Sürüyor · Bitti* +sütunlarında, kartlar sürükle-bırak ile taşınır, her değişiklik SQLite'a yazılır. + +```bash +cd web/pano +kabalang calistir pano.kbl +# → http://127.0.0.1:8471 +``` + +## Neyi gösteriyor + +| Konu | Nerede | +|---|---| +| `kaba.web` ile JSON REST API — `al` · `gonder` · `guncelle` · `kaldir` | `pano.kbl` sonu | +| Yol parametresi `/api/gorevler/{id}` | `guncelle`, `kaldir` | +| `kaba.veritabani` — **her sorgu parametre bağlamalı** (`?`) | tüm `veritabani.sqlite(..., [..])` çağrıları | +| Gelen JSON'u `json.gecerli_mi` + `json.al` ile okumak | `olustur` | +| Durum kodlu yanıtlar: 201 · 204 · 400 · 404 · 422 | `hata_json`, `web.json(g, 201)` | +| Sunucunun kendi hatalarının da JSON olması (405, 413) | `sunucu.hatalar_json()` | +| Kapalı küme denetimi (`SUTUNLAR.icerir(sutun)`) | `olustur`, `guncelle` | + +## API + +```bash +curl http://127.0.0.1:8471/api/gorevler +curl -X POST -d '{"baslik":"Yeni iş","oncelik":"yuksek"}' http://127.0.0.1:8471/api/gorevler +curl -X PUT -d '{"sutun":"bitti"}' http://127.0.0.1:8471/api/gorevler/3 +curl -X DELETE http://127.0.0.1:8471/api/gorevler/3 +``` + +## Güvenlik + +- **SQL enjeksiyonu:** kullanıcı verisi sorguya hiçbir zaman metin olarak eklenmez, `?` ile + bağlanır. `"x'); DROP TABLE gorev; --"` başlıklı bir görev yalnızca tuhaf adlı bir görevdir. +- **XSS:** ön yüz kart içeriğini `textContent` ile basar; ` + + diff --git a/web/pano/pano.kbl b/web/pano/pano.kbl new file mode 100644 index 0000000..7d9d91d --- /dev/null +++ b/web/pano/pano.kbl @@ -0,0 +1,158 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ PANO — KabaLang ile Kanban görev panosu ║ +# ║ ║ +# ║ · JSON REST API: GET · POST · PUT · DELETE /api/gorevler ║ +# ║ · Kalıcı kayıt: SQLite, her sorgu PARAMETRE BAĞLAMALI ║ +# ║ · Ön yüz: arayuz.html (tek sayfa, dış bağımlılık yok) ║ +# ╚══════════════════════════════════════════════════════════════════╝ +# +# Çalıştır: cd web/pano && kabalang calistir pano.kbl +# Aç: http://127.0.0.1:8471 + +kullan kaba.web +kullan kaba.json +kullan kaba.veritabani +kullan kaba.sistem + +# Panoda yalnız bu üç sütun vardır. Kapalı küme → istemciden gelen +# sütun adı bu listede değilse istek reddedilir. +SUTUNLAR = ["yapilacak", "suruyor", "bitti"] +ONCELIKLER = ["dusuk", "orta", "yuksek"] + +db = cevre.al("PANO_DB") +eger db == "": + db = "pano.db" + +# ── Veritabanı ────────────────────────────────────────────────────── + +fonksiyon hazirla(db): + veritabani.sqlite(db, "CREATE TABLE IF NOT EXISTS gorev(id INTEGER PRIMARY KEY AUTOINCREMENT, baslik TEXT NOT NULL, aciklama TEXT DEFAULT '', sutun TEXT NOT NULL, oncelik TEXT DEFAULT 'orta', olusturma TEXT);") + + # İlk açılışta pano boş görünmesin: birkaç örnek kart. + eger veritabani.sqlite(db, "SELECT count(*) FROM gorev") == "0": + ornekler = [ + ["Giriş ekranını tasarla", "Koyu tema, mobil öncelikli", "bitti", "yuksek"], + ["REST API taslağı", "GET · POST · PUT · DELETE uçları", "bitti", "orta"], + ["SQLite şeması", "Parametre bağlamalı sorgular", "suruyor", "yuksek"], + ["Sürükle-bırak kartlar", "Sütunlar arası taşıma", "suruyor", "orta"], + ["Bildirim e-postaları", "Görev atanınca haber ver", "yapilacak", "dusuk"], + ["Karanlık/aydınlık tema", "Sistem tercihine uy", "yapilacak", "orta"], + ["Yük testi", "1000 eşzamanlı istek", "yapilacak", "yuksek"] + ] + dongu o icinde ornekler: + veritabani.sqlite(db, "INSERT INTO gorev(baslik, aciklama, sutun, oncelik, olusturma) VALUES (?, ?, ?, ?, ?)", [o[0], o[1], o[2], o[3], zaman.simdi()]) + +# Bütün görevleri JSON dizi olarak verir. JSON'u SQLite'ın kendi +# json_object/json_group_array işlevleri kurar → elle kaçış yazmayız. +fonksiyon gorevler_json(db): + sonuc = veritabani.sqlite(db, "SELECT json_group_array(json_object('id', id, 'baslik', baslik, 'aciklama', aciklama, 'sutun', sutun, 'oncelik', oncelik, 'olusturma', olusturma)) FROM (SELECT * FROM gorev ORDER BY id)") + eger sonuc == "": + don "[]" + don sonuc + +fonksiyon gorev_json(db, kimlik): + don veritabani.sqlite(db, "SELECT json_object('id', id, 'baslik', baslik, 'aciklama', aciklama, 'sutun', sutun, 'oncelik', oncelik, 'olusturma', olusturma) FROM gorev WHERE id = ?", [kimlik]) + +# ── Doğrulama ─────────────────────────────────────────────────────── + +fonksiyon hata_json(ileti, durum): + don web.json(json.uret({"hata": ileti}), durum) + +# Kimlik yalnız rakamlardan oluşmalı; "1 OR 1=1" gibi bir değer +# zaten parametre olarak bağlanır, ama anlamsız istek erkenden döner. +fonksiyon kimlik_gecerli(k): + eger k.uzunluk() == 0 veya k.uzunluk() > 9: + don yanlis + dongu i icinde 0..k.uzunluk() - 1: + eger degil "0123456789".icerir(k.dilim(i, 1)): + don yanlis + don dogru + +# ── Uçlar ─────────────────────────────────────────────────────────── + +hazirla(db) + +arayuz = dosya.oku("arayuz.html") +eger arayuz == "": + yazdir("⚠ arayuz.html bulunamadı — programı web/pano klasöründen çalıştırın.") + +fonksiyon ana_sayfa(_istek): + don web.html(arayuz) + +fonksiyon listele(_istek): + don web.json(gorevler_json(db)) + +fonksiyon olustur(istek): + govde = istek.govde() + eger degil json.gecerli_mi(govde): + don hata_json("gövde geçerli JSON değil", 400) + baslik = json.al(govde, "baslik").kirp() + aciklama = json.al(govde, "aciklama").kirp() + sutun = json.al(govde, "sutun") + oncelik = json.al(govde, "oncelik") + eger baslik == "": + don hata_json("başlık boş olamaz", 422) + eger baslik.uzunluk() > 120: + don hata_json("başlık en çok 120 karakter", 422) + eger sutun == "": + sutun = "yapilacak" + eger oncelik == "": + oncelik = "orta" + eger degil SUTUNLAR.icerir(sutun): + don hata_json("bilinmeyen sütun: " + sutun, 422) + eger degil ONCELIKLER.icerir(oncelik): + don hata_json("bilinmeyen öncelik: " + oncelik, 422) + + veritabani.sqlite(db, "INSERT INTO gorev(baslik, aciklama, sutun, oncelik, olusturma) VALUES (?, ?, ?, ?, ?)", [baslik, aciklama, sutun, oncelik, zaman.simdi()]) + yeni = veritabani.sqlite(db, "SELECT max(id) FROM gorev") + don web.json(gorev_json(db, yeni), 201) + +fonksiyon guncelle(istek): + kimlik = istek.parametre("id") + eger degil kimlik_gecerli(kimlik): + don hata_json("geçersiz kimlik", 400) + eger veritabani.sqlite(db, "SELECT count(*) FROM gorev WHERE id = ?", [kimlik]) == "0": + don hata_json("görev bulunamadı", 404) + govde = istek.govde() + eger degil json.gecerli_mi(govde): + don hata_json("gövde geçerli JSON değil", 400) + + # Yalnız gönderilen alanlar güncellenir. + sutun = json.al(govde, "sutun") + eger sutun != "": + eger degil SUTUNLAR.icerir(sutun): + don hata_json("bilinmeyen sütun: " + sutun, 422) + veritabani.sqlite(db, "UPDATE gorev SET sutun = ? WHERE id = ?", [sutun, kimlik]) + baslik = json.al(govde, "baslik").kirp() + eger baslik != "": + veritabani.sqlite(db, "UPDATE gorev SET baslik = ? WHERE id = ?", [baslik, kimlik]) + oncelik = json.al(govde, "oncelik") + eger oncelik != "": + eger degil ONCELIKLER.icerir(oncelik): + don hata_json("bilinmeyen öncelik: " + oncelik, 422) + veritabani.sqlite(db, "UPDATE gorev SET oncelik = ? WHERE id = ?", [oncelik, kimlik]) + don web.json(gorev_json(db, kimlik)) + +fonksiyon kaldir(istek): + kimlik = istek.parametre("id") + eger degil kimlik_gecerli(kimlik): + don hata_json("geçersiz kimlik", 400) + eger veritabani.sqlite(db, "SELECT count(*) FROM gorev WHERE id = ?", [kimlik]) == "0": + don hata_json("görev bulunamadı", 404) + veritabani.sqlite(db, "DELETE FROM gorev WHERE id = ?", [kimlik]) + don web.yanit("", 204) + +# Tek iş parçacığı: SQLite'a yazan işleyiciler sırayla koşar. +sunucu = web.sunucu(8471, "127.0.0.1", 1) +sunucu.hatalar_json() +sunucu.govde_siniri(16384) + +sunucu.al("/", ana_sayfa) +sunucu.al("/api/gorevler", listele) +sunucu.gonder("/api/gorevler", olustur) +sunucu.guncelle("/api/gorevler/{id}", guncelle) +# DELETE yöntemi `sunucu.kaldir` ile bağlanır +sunucu.kaldir("/api/gorevler/{id}", kaldir) + +yazdir("📋 Pano hazır — http://127.0.0.1:8471") +sunucu.basla()