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.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
2026-10-02 22:31:40 +03:00
co-authored by Claude Opus 5.5
commit ed201188fb
39 changed files with 2650 additions and 0 deletions
+18
View File
@@ -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
+67
View File
@@ -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 <dosya.kbl> -o <ikili>`.
Her projenin klasöründe ayrıntılı bir README vardır.
+25
View File
@@ -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.
+337
View File
@@ -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"
+26
View File
@@ -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")` |
+155
View File
@@ -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"
+32
View File
@@ -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.
+329
View File
@@ -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<metin>:
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)])
+39
View File
@@ -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<T, Hata>` | `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.
+179
View File
@@ -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<metin, Hata>:
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)
+22
View File
@@ -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 }
]
}
+39
View File
@@ -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.
+171
View File
@@ -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<metin>:
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)
+39
View File
@@ -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<sayi> = []`, `durum: liste<Hucre> = []` |
| `**` ü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.
+197
View File
@@ -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<sayi> = []
durum: liste<Hucre> = []
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")
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+35
View File
@@ -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<Secenek>`) | `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`).
+197
View File
@@ -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<Secenek>:
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 = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 " + metin(gen) + " " + metin(yuk) + "' font-family='Inter, system-ui, sans-serif'>"
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 + "<text x='0' y='" + metin(y + 14) + "' fill='#e6ebf5' font-size='15' font-weight='" + kalinlik + "'>" + web.kacir(sec.ad) + "</text>"
s = s + "<rect x='0' y='" + metin(y + 24) + "' width='" + metin(gen - 120) + "' height='22' rx='11' fill='#182033'/>"
s = s + "<rect x='0' y='" + metin(y + 24) + "' width='" + metin(cubuk) + "' height='22' rx='11' fill='" + sec.renk + "'/>"
s = s + "<text x='" + metin(gen) + "' y='" + metin(y + 41) + "' fill='#e6ebf5' font-size='17' font-weight='700' text-anchor='end'>%" + metin(yuzde) + "</text>"
s = s + "<text x='" + metin(gen - 52) + "' y='" + metin(y + 41) + "' fill='#8a94ab' font-size='13' text-anchor='end'>" + metin(sec.oy) + "</text>"
i = i + 1
don s + "</svg>"
# ── 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 = "<meta http-equiv='refresh' content='5'>"
don "<!doctype html><html lang='tr'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'>" + ek + "<title>Anket · KabaLang</title><style>" + STIL + "</style></head><body><main class='kart'>" + govde + "</main></body></html>"
fonksiyon ust_bant(toplam):
don "<div class='ust'><b style='color:#e6ebf5'>KabaLang Anket</b> · " + metin(toplam) + " oy<span class='canli'>canlı</span></div>"
# ── Uçlar ───────────────────────────────────────────────────────────
hazirla(db)
fonksiyon sonuc_sayfasi(istek):
liste = secenekler(db)
benim = verdigi_oy(db, istek.adres())
g = ust_bant(toplam_oy(liste)) + "<h1>" + web.kacir(SORU) + "</h1>"
eger istek.sorgu("zaten") == "1":
g = g + "<div class='bilgi'>Bu adresten zaten oy verilmiş — ikinci oy sayılmadı.</div>"
degilse eger benim != "":
g = g + "<div class='bilgi'>✓ Oyunuz kaydedildi. Sonuçlar 5 saniyede bir tazelenir.</div>"
g = g + "<div class='genis'>" + grafik_svg(liste, benim, 760) + "</div>"
g = g + "<div class='dar'>" + grafik_svg(liste, benim, 380) + "</div>"
g = g + "<div class='alt'><span>Grafik sunucuda KabaLang ile çizildi · <a href='/sonuc.svg'>sonuc.svg</a> · <a href='/api/sonuclar'>JSON</a></span><a href='/'>← ankete dön</a></div>"
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)) + "<h1>" + web.kacir(SORU) + "</h1><div class='secenekler'>"
dongu s icinde liste:
g = g + "<form method='post' action='/oy/" + metin(s.kimlik) + "'><button><span class='top' style='background:" + s.renk + "'></span>" + web.kacir(s.ad) + "</button></form>"
g = g + "</div><div class='alt'><span>Her adres bir kez oy verebilir.</span><a href='/sonuc'>sonuçları gör →</a></div>"
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("<h1>Böyle bir seçenek yok</h1><div class='alt'><a href='/'>← ankete dön</a></div>", 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()
+38
View File
@@ -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)
+285
View File
@@ -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 "<!doctype html><html lang='tr'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>" + web.kacir(baslik) + "</title><style>" + STIL + "</style></head><body><div class='kap'><nav><div class='logo'>K</div><b>Kısalt</b><span>KabaLang · SQLite · 302</span></nav>" + govde + "<footer>Bu sayfa sunucuda KabaLang ile üretildi.</footer></div></body></html>"
# ── 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 = "<section class='kahraman'><h1>Uzun bağlantılar,<br><em>kısa ve ölçülebilir.</em></h1>"
g = g + "<p>Bir adres yapıştırın; KabaLang ona kısa bir kod versin ve her tıklamayı saysın.</p>"
g = g + "<form id='f'><input id='adres' placeholder='https://…' autocomplete='off' required><button>Kısalt</button></form>"
g = g + "<div id='sonuc'></div></section>"
g = g + "<div class='kutular'>"
g = g + "<div class='kutu'><small>Bağlantı</small><div>" + toplam + "</div></div>"
g = g + "<div class='kutu'><small>Toplam tıklama</small><div>" + tik + "</div></div>"
g = g + "<div class='kutu'><small>Bugün</small><div>" + bugun + "</div></div>"
g = g + "</div>"
g = g + "<table><tr><th>Kısa kod</th><th class='hedef'>Hedef</th><th>Tıklama</th><th></th></tr>"
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 + "<tr><td class='kod'><a href='/k/" + kod + "'>/k/" + kod + "</a></td>"
g = g + "<td class='hedef'>" + web.kacir(son_sutun(p, 2)) + "</td>"
g = g + "<td class='sayac'>" + p[1] + "</td>"
g = g + "<td><a href='/istatistik/" + kod + "'>istatistik →</a></td></tr>"
g = g + "</table>"
# Küçük bir istemci betiği: formu JSON API'ye gönderir.
g = g + "<script>document.getElementById('f').onsubmit=async e=>{e.preventDefault();const s=document.getElementById('sonuc');"
g = g + "const y=await fetch('/api/kisalt',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({adres:document.getElementById('adres').value})});"
g = g + "const d=await y.json();s.replaceChildren();if(!y.ok){s.textContent='⚠ '+d.hata;return;}"
g = g + "const a=document.createElement('a');a.className='kisa';a.href=d.kisa;a.textContent=location.host+d.kisa;s.append('✓ Hazır: ',a);setTimeout(()=>location.reload(),1600);};</script>"
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 = "<section class='kahraman'><h1>Bu kod <em>bulunamadı</em></h1><p>Bağlantı silinmiş ya da hiç var olmamış olabilir.</p><p><a href='/'>← ana sayfa</a></p></section>"
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 viewBox='0 0 " + metin(gen) + " " + metin(yuk + 40) + "' xmlns='http://www.w3.org/2000/svg'>"
svg = svg + "<defs><linearGradient id='g' x1='0' y1='1' x2='0' y2='0'><stop offset='0' stop-color='#377ef4'/><stop offset='1' stop-color='#22d3ee'/></linearGradient></defs>"
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 + "<rect x='" + metin(x) + "' y='" + metin(y) + "' width='" + metin(cubuk) + "' height='" + metin(h) + "' rx='10' fill='url(#g)'/>"
svg = svg + "<text x='" + metin(x + cubuk // 2) + "' y='" + metin(y - 8) + "' fill='#e6ebf5' font-size='15' font-weight='700' text-anchor='middle' font-family='system-ui'>" + metin(adetler[i]) + "</text>"
svg = svg + "<text x='" + metin(x + cubuk // 2) + "' y='" + metin(yuk + 26) + "' fill='#8a94ab' font-size='13' text-anchor='middle' font-family='system-ui'>" + gunler[i] + "</text>"
don svg + "</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 = "<section class='kahraman' style='padding-bottom:10px'><h1><em>/k/" + kod + "</em></h1>"
g = g + "<p class='adres'>→ " + web.kacir(son_sutun(p, 2)) + "</p></section>"
g = g + "<div class='kutular'>"
g = g + "<div class='kutu'><small>Toplam tıklama</small><div>" + p[0] + "</div></div>"
g = g + "<div class='kutu'><small>Oluşturuldu</small><div style='font-size:17px;padding-top:7px'>" + p[1] + "</div></div>"
g = g + "<div class='kutu'><small>Son tıklama</small><div style='font-size:17px;padding-top:7px'>" + son + "</div></div>"
g = g + "</div>"
g = g + "<div class='grafik'><small style='color:#8a94ab;text-transform:uppercase;letter-spacing:.07em;font-size:12px'>Son 7 gün</small>" + yedi_gun_svg(db, kod) + "</div>"
g = g + "<p style='text-align:center;margin-top:22px'><a href='/'>← tüm bağlantılar</a></p>"
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()
+45
View File
@@ -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; `<script>` başlıklı bir görev ekranda
düz yazı olarak görünür.
- Sunucu yalnız `127.0.0.1`'i dinler ve tek iş parçacığıyla çalışır (SQLite'a sırayla yazılır).
Veritabanı yolu `PANO_DB` ortam değişkeniyle değiştirilebilir (varsayılan `pano.db`).
![Mobil](../../medya/pano-mobil.png)
+217
View File
@@ -0,0 +1,217 @@
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pano · KabaLang</title>
<style>
:root {
--zemin: #0a0d15; --yuzey: #121826; --yuzey2: #182033; --cizgi: #232c42;
--metin: #e6ebf5; --soluk: #8a94ab; --mavi: #377ef4; --cam: #22d3ee;
--yesil: #34d399; --sari: #fbbf24; --kirmizi: #f87171;
}
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh; color: var(--metin);
font: 15px/1.5 Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
background:
radial-gradient(900px 500px at 10% -10%, #377ef42a, transparent 60%),
radial-gradient(700px 400px at 100% 0%, #22d3ee1c, transparent 60%),
var(--zemin);
}
header {
display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
padding: 22px 32px; border-bottom: 1px solid var(--cizgi);
backdrop-filter: blur(8px);
}
.logo {
width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
background: linear-gradient(135deg, var(--mavi), var(--cam)); font-weight: 800; color: #fff;
box-shadow: 0 6px 22px #377ef455;
}
h1 { font-size: 20px; margin: 0; letter-spacing: -.01em; }
.alt { color: var(--soluk); font-size: 13px; }
.rozet {
margin-left: auto; font-size: 12px; color: var(--cam); border: 1px solid #22d3ee44;
padding: 5px 11px; border-radius: 999px; background: #22d3ee10;
}
main { padding: 26px 32px 40px; max-width: 1360px; margin: 0 auto; }
form.ekle {
display: grid; grid-template-columns: 2fr 2fr 1fr auto; gap: 10px;
background: var(--yuzey); border: 1px solid var(--cizgi); border-radius: 16px; padding: 14px;
margin-bottom: 24px;
}
input, select, button {
font: inherit; color: var(--metin); background: var(--yuzey2);
border: 1px solid var(--cizgi); border-radius: 10px; padding: 10px 12px; min-width: 0;
}
input:focus, select:focus { outline: 2px solid #377ef488; border-color: transparent; }
button {
background: linear-gradient(135deg, var(--mavi), #2563eb); border: 0; font-weight: 600;
cursor: pointer; padding: 10px 18px; box-shadow: 0 6px 18px #377ef440;
}
.sutunlar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.sutun {
background: #121826cc; border: 1px solid var(--cizgi); border-radius: 18px;
padding: 14px; min-height: 380px; transition: border-color .15s, background .15s;
}
.sutun.ustunde { border-color: var(--mavi); background: #377ef412; }
.sutun h2 {
font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--soluk);
margin: 4px 6px 14px; display: flex; align-items: center; gap: 8px;
}
.nokta { width: 9px; height: 9px; border-radius: 50%; }
.sayi { margin-left: auto; background: var(--yuzey2); border-radius: 999px; padding: 1px 9px; font-size: 12px; }
.kart {
background: var(--yuzey2); border: 1px solid var(--cizgi); border-radius: 14px;
padding: 12px 14px; margin-bottom: 10px; cursor: grab; position: relative;
transition: transform .12s, box-shadow .12s;
}
.kart:hover { transform: translateY(-2px); box-shadow: 0 10px 26px #0008; }
.kart.sur { opacity: .45; }
.kart b { display: block; font-weight: 600; padding-right: 26px; }
.kart p { margin: 4px 0 10px; color: var(--soluk); font-size: 13px; }
.alt-satir { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--soluk); }
.etiket { padding: 2px 9px; border-radius: 999px; font-weight: 600; font-size: 11px; }
.yuksek { color: var(--kirmizi); background: #f8717118; }
.orta { color: var(--sari); background: #fbbf2418; }
.dusuk { color: var(--yesil); background: #34d39918; }
.sil {
position: absolute; top: 8px; right: 8px; background: none; box-shadow: none; color: var(--soluk);
padding: 2px 8px; font-size: 16px; opacity: 0; transition: opacity .15s;
}
.kart:hover .sil { opacity: 1; }
.tasi { display: none; gap: 6px; margin-top: 8px; }
.tasi button { padding: 5px 10px; font-size: 12px; box-shadow: none; background: var(--yuzey); border: 1px solid var(--cizgi); }
footer { text-align: center; color: var(--soluk); font-size: 12px; padding: 0 0 28px; }
footer code { color: var(--cam); }
@media (max-width: 820px) {
header, main { padding-left: 16px; padding-right: 16px; }
form.ekle { grid-template-columns: 1fr 1fr; }
form.ekle input[name=baslik], form.ekle button { grid-column: 1 / -1; }
.sutunlar { grid-template-columns: 1fr; }
.sutun { min-height: 0; }
.tasi { display: flex; }
.sil { opacity: 1; }
}
</style>
</head>
<body>
<header>
<div class="logo">K</div>
<div>
<h1>Proje Panosu</h1>
<div class="alt">Sunucusu KabaLang ile yazıldı · SQLite · REST API</div>
</div>
<span class="rozet" id="ozet">yükleniyor…</span>
</header>
<main>
<form class="ekle" id="form">
<input name="baslik" placeholder="Yeni görev başlığı" maxlength="120" required>
<input name="aciklama" placeholder="Kısa açıklama (isteğe bağlı)" maxlength="200">
<select name="oncelik">
<option value="orta">Orta öncelik</option>
<option value="yuksek">Yüksek öncelik</option>
<option value="dusuk">Düşük öncelik</option>
</select>
<button type="submit">+ Ekle</button>
</form>
<div class="sutunlar" id="sutunlar"></div>
</main>
<footer>Kartları sütunlar arasında sürükleyin · <code>GET /api/gorevler</code></footer>
<script>
const SUTUNLAR = [
["yapilacak", "Yapılacak", "#8a94ab"],
["suruyor", "Sürüyor", "#377ef4"],
["bitti", "Bitti", "#34d399"],
];
const ONCELIK = { yuksek: "Yüksek", orta: "Orta", dusuk: "Düşük" };
const kok = document.getElementById("sutunlar");
let gorevler = [];
async function api(yontem, yol, govde) {
const y = await fetch(yol, {
method: yontem,
headers: govde ? { "Content-Type": "application/json" } : {},
body: govde ? JSON.stringify(govde) : undefined,
});
if (!y.ok && y.status !== 204) throw new Error((await y.json()).hata || y.status);
return y.status === 204 ? null : y.json();
}
function el(etiket, sinif, metin) {
const e = document.createElement(etiket);
if (sinif) e.className = sinif;
if (metin !== undefined) e.textContent = metin; // textContent → XSS yok
return e;
}
function ciz() {
kok.replaceChildren();
for (const [ad, baslik, renk] of SUTUNLAR) {
const liste = gorevler.filter(g => g.sutun === ad);
const s = el("section", "sutun");
s.dataset.sutun = ad;
const h = el("h2");
const n = el("span", "nokta"); n.style.background = renk;
h.append(n, baslik, el("span", "sayi", liste.length));
s.append(h);
for (const g of liste) s.append(kart(g));
s.addEventListener("dragover", e => { e.preventDefault(); s.classList.add("ustunde"); });
s.addEventListener("dragleave", () => s.classList.remove("ustunde"));
s.addEventListener("drop", e => {
e.preventDefault(); s.classList.remove("ustunde");
tasi(+e.dataTransfer.getData("text/plain"), ad);
});
kok.append(s);
}
const bitti = gorevler.filter(g => g.sutun === "bitti").length;
document.getElementById("ozet").textContent =
`${gorevler.length} görev · %${gorevler.length ? Math.round(100 * bitti / gorevler.length) : 0} tamam`;
}
function kart(g) {
const k = el("article", "kart");
k.draggable = true;
k.addEventListener("dragstart", e => { e.dataTransfer.setData("text/plain", g.id); k.classList.add("sur"); });
k.addEventListener("dragend", () => k.classList.remove("sur"));
k.append(el("b", "", g.baslik));
if (g.aciklama) k.append(el("p", "", g.aciklama));
const alt = el("div", "alt-satir");
alt.append(el("span", "etiket " + g.oncelik, ONCELIK[g.oncelik] || g.oncelik), "#" + g.id);
k.append(alt);
const sil = el("button", "sil", "×");
sil.title = "Sil";
sil.onclick = async () => { await api("DELETE", "/api/gorevler/" + g.id); yukle(); };
k.append(sil);
const t = el("div", "tasi");
for (const [ad, baslik] of SUTUNLAR) if (ad !== g.sutun) {
const b = el("button", "", "→ " + baslik);
b.onclick = () => tasi(g.id, ad);
t.append(b);
}
k.append(t);
return k;
}
async function tasi(id, sutun) {
await api("PUT", "/api/gorevler/" + id, { sutun });
yukle();
}
async function yukle() { gorevler = await api("GET", "/api/gorevler"); ciz(); }
document.getElementById("form").addEventListener("submit", async e => {
e.preventDefault();
const f = new FormData(e.target);
await api("POST", "/api/gorevler", {
baslik: f.get("baslik"), aciklama: f.get("aciklama"), oncelik: f.get("oncelik"), sutun: "yapilacak",
});
e.target.reset();
yukle();
});
yukle();
</script>
</body>
</html>
+158
View File
@@ -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()