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]>
@@ -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
|
||||
@@ -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/)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Konsol
|
||||
|
||||
| | Proje | Ne gösteriyor |
|
||||
|---|---|---|
|
||||
|  | [**Fraktal**](konsol/fraktal) | 24-bit renkli Mandelbrot ve Julia; `birlikte:` + `baslat` ile dört şerit **paralel** hesaplanır, `--yakinlas` ile 120 karelik dalış |
|
||||
|  | [**Sudoku**](konsol/sudoku) | Geri izlemeli çözücü, adım adım canlı animasyon; `sinif` ve `sayilabilir` hücre durumu |
|
||||
|  | [**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 |
|
||||
|---|---|---|
|
||||
|  | [**Hesap makinesi**](arayuz/hesap-makinesi) | İşlem önceliğini bilen hesap motoru; ekran **canlı bağlama** ile kendiliğinden güncellenir |
|
||||
|  | [**Odak**](arayuz/odak) | Pomodoro zamanlayıcı: `zamanlayici` bloğu, ilerleme çubuğu, oturum kaydı, masaüstü bildirimi |
|
||||
|  | [**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 |
|
||||
|---|---|---|
|
||||
|  | [**Pano**](web/pano) | Kanban panosu: JSON REST API (GET · POST · PUT · DELETE) + SQLite + tek sayfalık ön yüz |
|
||||
|  | [**Kısalt**](web/kisalt) | Bağlantı kısaltıcı: gerçek 302 yönlendirme, tıklama sayacı, sunucuda çizilen SVG grafik |
|
||||
|  | [**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.
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
```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.
|
||||
@@ -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"
|
||||
@@ -0,0 +1,26 @@
|
||||
# Odak — Pomodoro zamanlayıcı
|
||||
|
||||
Odaklan, mola ver, günün oturumlarını kaydet. Oturum bitince masaüstü
|
||||
bildirimi gelir.
|
||||
|
||||

|
||||
|
||||
```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")` |
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
```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.
|
||||
@@ -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)])
|
||||
@@ -0,0 +1,39 @@
|
||||
# Fatura — JSON siparişten terminal tablosu, PDF ve Excel
|
||||
|
||||

|
||||
|
||||
`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.
|
||||
@@ -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)
|
||||
@@ -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 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Fraktal — terminalde 24 bit renkli Mandelbrot ve Julia
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -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)
|
||||
@@ -0,0 +1,39 @@
|
||||
# Sudoku — geri izlemeli çözücü, canlı animasyonla
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -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")
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1,35 @@
|
||||
# Anket — canlı oylama
|
||||
|
||||

|
||||
|
||||
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` |
|
||||
|
||||

|
||||
|
||||
## 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`).
|
||||
@@ -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()
|
||||
@@ -0,0 +1,38 @@
|
||||
# Kısalt — bağlantı kısaltıcı
|
||||
|
||||

|
||||
|
||||
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 |
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
@@ -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()
|
||||
@@ -0,0 +1,45 @@
|
||||
# Pano — Kanban görev panosu
|
||||
|
||||

|
||||
|
||||
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`).
|
||||
|
||||

|
||||
@@ -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>
|
||||
@@ -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()
|
||||