30 Aralık 2008 Salı

Konsol uygulamaları için arguman ayırıcı

Aslında birkaç haftadır aklımdaydı bir komut satırı ayırıcısı yazmak. Fakat bir türlü bir iki gün ayırıp başlayamamıştım bu işe. En sonunda zaman bu zamandır dedim ve yazdım bir prototip. Aslında bu kararı böylesine kesinlikle alışımda finallerin stresinden kurtulma fikri etkili oldu diyebilirim. Programcıların bir çoğu program yazarak rahatlar :) Her neyse, şimdilik komut satırı ayrıcımız sade ve ufak, fakat GNU komut satırı uygulamalarında uygulanan komut argüman stiliyle uyumlu çalışıyor. Bununla beraber üzülerek belirtmeliyim ki henüz harici bir dökümantasyona sahip değil. Kaynak kod içinde mümkün olduğunca açıklama yazmaya çalıştım. Fakat sınav meşguliyeti dolayısıyla bu işe çok kısa bir süre ayırabildiğimden pek vakıf olabildiğimi söyleyemem. Eğer projelerinde bu kodu kullanmak isteyen varsa şimdilik ufak bir örnek program ve kaynak kod açıklamalarıyla idare etmek zorunda. Ayrıca kullanmayı düşünenler olursa diye ufak kütüphanemizin temel çalışma prensibini açıklayıp, örneklemek istedim.

Öncelikle kütüphane’nin temel hedefi komut satırı uygulamalarını yazarken argüman ayırma derdine bir çözüm olmasıdır. Yani

ls -la /home
ps -aux
diff --brief -biw file1 file2

gibi komut satırı çağrılarında kullanılabilecek farklı formlardaki argumanları işleyebilmemize yarar. Bununla birlikte kütüphanin kullanımı Callback Mechanizm adı verilen bir programlama tekniğine dayanır. Ne demek bu Callback Mechanizm? Kısaca geri çağrı mekanizması olarak açıklayabiliriz. Yani bir olayın gerçekleşmesine bağlı olan eylem(fonksiyon) çağrısı mekanizması. MVC mimarisine aşina olanlar için bu mekanizma zaten gayet tanıdıktır. Çünkü bu sistem olay temelli programlamada çok yaygın kullanılır. (Popüler Qt kütüphanesi benzer fakat farklı bir mekanizma kullanır SIGNAL/SLOT) Konumuzdan fazla uzaklaştırmadan kütüphanemizi açıklamaya devam edersek. Kütüphane C++ dilinin nesne yönelimli kullanımına bir örnek teşkil eder. Basit bir struct ve bir class tanımından meydana gelir. Kütüphaneye dahil olan tüm tanımlar CLParser namespace’i içinde tanımlanmıştır. Şimdi kütüphanin kullanımına tipik bir önrek vereyim :

int main(int argc, char **argv) {
CLParser::CLParser clp(argc, argv); // Nesne tanımı
clp.setShortPrefix("-"); // Kısa parametre ön eki
clp.setLongPrefix("--"); // Uzun parametre ön eki
clp.setMaxNotPrefixedCC(2); // Maks. ön eksiz argüman sayısı(dosya ismi vb.)
// Argumanları ayıkla ve gerekli fonksiyon çağrılarını gerçekleştir
clp.parse();

return 0;
}

İşte bu kadar basit. Aslında örnek programımızda setShortPrefix, setLongPrefix ve setMaxNotPrefixedCC fonksiyonlarının kullanımının gereksiz olduğunu söyleyebilirim. Çünkü zaten ön tanımlı olarak kısa parametre ön eki "-" ve uzun parametre ön eki "--" olarak belirlenmiştir. Ayrıca yine maksimum ön eksiz argüman sayısı varsayılan olarak 1 olarak belirlenmiştir. Bu kodlarıda varsayılan değerlerin kullanımı vasıtasıyla kaldırdığımızda örnek programımız :

int main(int argc, char **argv) {
CLParser::CLParser clp(argc, argv); // Nesne tanımı
// Argumanları ayıkla ve gerekli fonksiyon çağrılarını gerçekleştir
clp.parse();

return 0;
}

Bir hayli sade değil mi? :) Peki şimdi şu soruyu sorabilirsiniz. "İşlev nerede? Yani tamam kütüphane argumanları parse ediyor fakat işlev?" İşlevsellik için daha önce belirtilen callback mekanizmasını kullanıyoruz. Kütüphanemizle birlikte gelen CLCommands.h ve CLCommands.cpp dosyaları üzerinde bir kaç değişiklik yapmamız yeterli. Bu dosyalar parser(ayırıcı)'ımızın ayırdığı belli argümanlara karşı hangi callback fonksiyonlarının eşleştiğini belirlememize yarıyor.

Aşağıda CLCommands.h dosyasının birazcık sadeleştirilmiş ve türkçeye çevrilmiş bir versiyonu mevcut. İncelediğinizde kütüphanenin nasıl kullanıldığını anlayacağınızı umuyorum.

namespace CLParser {

///////////////////////////////////////
// GERİ ÇAĞRI FONKSİYON PROTOTİPLERİ //
///////////////////////////////////////

/*
Tüm geri çağrı fonksiyonları aşağıdaki prototiple aynı imzayı taşımalıdır.
(Aynı geri dönüş ve giriş argümanı tip ve sıralamasını kullanmalıdır)
void func(char *command, int argc, char **args);
*/

void cbNPArgs(char *command, int argc, char **args);
void cbA(char *command, int argc, char **args);
void cbB(char *command, int argc, char **args);
void cbC(char *command, int argc, char **args);
void cbDE(char *command, int argc, char **args);
void cbDEF(char *command, int argc, char **args);
void cbDEFG(char *command, int argc, char **args);

//////////////////////////////////////////////////////////////////////////////////
// GERİ ÇAĞRI TABLOSU (TÜM GERİ ÇAĞRI METODLARI BU NOKTADA PARSER İLE BİRLEŞİR) //
//////////////////////////////////////////////////////////////////////////////////

const CLCommand COMMAND_SET[] = {

{"a", cbA, 2}, // Kısa ön ekli "a" argümanı. 2 parametre alır. Geri çağrı fonksiyonu "cbA"
{"A", cbA, 2}, // "a" komutuna kısa yol.(alias)

{"b", cbB, 3}, // Kısa ön ekli "b" argümanı. 3 parametre alır. Geri çağrı fonksiyonu "cbB"
{"c", cbC}, // Kısa ön ekli "c" argümanı. Parametre almaz. Geri çağrı fonksiyonu "cbC"

{"de", cbDE, 2}, // Uzun ön ekli "de" argümanı. 2 parametreli. Geri çağrı fonksiyonu "cbDE"
{"Foo", cbDE, 4}, // Uzun ön ekli "Foo" argümanı. "de" argümanıyla aynı geri çağrı fonksiyonunu kullanır. 4 parametreli.

{"def", cbDEF, 3}, // Uzun ön ekli "def" argümanı. 3 parametre alır. Geri çağrı fonksiyonu "cbDEF"
{"defg", cbDEFG}, // Uzun ön ekli "defg" command. Parametre almaz. Geri çağrı fonksiyonu "cbDEFG"

/*
Bu komut özel bir anlam ifade eder.
Ön ek almamış herhangi bir argumanı ifade eder. (dosya isimleri vb.)
*/
{"*", cbNPArgs} // Ön ek almamış herhangi bir arguman (dosya ismi gibi). Geri çağrı fonksiyonu "cbNPArgs"
};

} // namespace CLParser

Görüldüğü üzere kullanımı oldukça basit. Tek yapmanız gereken bir karakter(kısa ön ek kullanır) yada birden çok karakter uzunluğunda bir arguman(uzun ön ek kullanır) ve parser'ın bu arguman'a rastladığında çağrıcağı fonksiyonu tanımlamaktır. Ayrıca tercihen arguman'ın alabileceği bir parametre sayısıda verebilirsiniz. Örneğin

komut -a 1 2

Yukarıda ki komut satırı çağrısı bir adet arguman("a") ve bu argumana ait iki parametre("1", "2") alır.

Parser otomatik olarak "a" argümanıyla eşleşen geri çağrı fonksiyonunu çağarıcaktır ve bu geri çağrı fonksiyonuna

void geri_cagri_fonk(char *command, int argc, char **args);
^ ^ ^
ARGÜMAN'IN ARGÜMAN'IN ARGÜMAN'IN
KENDİSİ PARAMETRE PARAMETRE
SAYISI LİSTESİ

şeklinde iletilir.

Parser kısa ve uzun ön ekli argümanların olası sayıda parametrelerini destekler. Bunlara ek olarak kısa ön ekli argümanların birleştirilmiş yazımınıda desteklemektedir. Fakat tahmin edeceğiniz gibi bu durumda birleştirilen argümanlar parametre almayan türden olmalıdır. Örneğin

ls -l -a
ls -la yada ls -al

biçminde yazılabilir. Unutulmamalıdır ki burada -l ve -a parametrelerinin birleştirilebilmeleri için parametre almayan türden kısa ön ekli argümanlar olmaları gerekmektedirler.

Yazımı sonlandırırken belirtmem gereken bir nokta da geri çağrı prototiplerinin nerede implemente ediliceğidir. Ben bu iş için CLCommands.cpp dosyasını kullanıyorum. Fakat siz kendi tercihinizi yapmakta özgürsünüz.

Yazımın açıklayıcı olduğunu umuyorum. Proje kodu GPL lisansı sahiptir. İstediğiniz değişikliği yapmakta özgürsünüz. Bu uzunlukta bir metini sıkılmadan okuduğunuz için size ayrıca teşekkür ediyorum.. İyi eğlenceler..

PROJE KODUNU INDIR