.NET Core ve System.CommandLine ile Komut Satırı Uygulamaları Geliştirmek

Herkese selamlar. Bu yazı’da .NET Core ile birlikte üçüncü parti kütüphanelere ya da manuel yöntemlere gerek duymaksızın nasıl CLI uygulamalar geliştirebileceğimize değinmek istiyorum.
Daha önce commandlineparser kullandınız mı bilmiyorum. Ancak enuygun’dan önceki firmamda, bir süreliğine kullanma durumum olmuştu mono kullanarak.
Hep içten içe bu işi daha çok kolaylaştıran bir kütüphane olması gerektiğini düşünüyordum. Haliyle kendi kütüphanemi yapmaya karar verdim ve bok gibi bir şey ortaya çıktı. Zaten hiçbir yerde de göremezsiniz onu.
Daha sonraları ise Microsoft mühendisleri ve topluluk gerçekten çok tatlış bir kütüphane ortaya çıkartmış. Kullanımı kolay, dokümantasyonu güzel olan bir kütüphane yani System.CommandLine kütüphanesi.
Kısaca System.CommandLine
Öncelikle şu anda System.CommandLine preview olarak yayında. Yani kurarken “Include prerelease” demeniz gerekiyor.
Bu kütüphane temel olarak bir CLI uygulaması yapma esnasında ne gerekiyor ise onu size sağlıyor. Mesela CLI input parse etmek ya da bir description göstermek gibi.
Prerelease dedim ancak şu anda .NET CLI, .NET için uninstall, diagnostic tool, svcutil vs. gibi birçok araç yine bu kütüphaneyi kullanıyor.
Ayrıca bu kütüphane şu anda AOT desteği ile birlikte geliyor. Kısacası modern özelliklerle size CLI app geliştirmek konusunda yardımcı olabilir.
Yine bu kütüphane POSIX ya da Windows kurallarına göre inputların kontrolünden de emin oluyor.
Tab completion ve response file desteği de yine bu kütüphane ile gelmekte.
Kurulum
Eğer Visual Studio kullanıyorsanız Nuget paketlerine prerelease olanları da dahil ederek System.CommandLine olarak arama yapın. Yok kullanmıyor iseniz şöyle de kurabilirsiniz
dotnet add package System.CommandLine --prerelease
Örnek Bir Proje — Docker Image Search
Örneğin bir projede oluşturduk ve bu projenin amacı da Docker üzerinden image araması gerçekleştirmek. Yani aslında birden çok özelliği var fakat bu projede image search yapalım. Hem yukarıdaki kütüphaneyi hem de Docker kütüphanesini kuralım
dotnet add package Docker.DotNet
Şimdi gelelim kütüphanenin kullanımına. Öncelikle System.CommandLine kütüphanesi option dediğimiz, command’lara passlanabilen named parameter dediğimiz özelliklere sahiptir. Ya da yapı diyeyim.
Dinamik olarak type bind edilebilen generic türlerdir. Örneğin bir image’in adını alacağımız option’ı yazmamız gerekirse şöyle olur;
Option Tanımlamak
var dockerImageNameOption = new Option(
name: "--image-name",
description: "Image name to search"
);
Gördüğünüz gibi eğer bir “— image-name” komutu gönderirsek bunun türü string olmalıdır diyebiliyoruz. Eğer bir option’ın T değeri nullable değil ise required olarak tanımlanmış demektir. Mesela search aşamasında bir de limit belirtelim. Yani bize tek seferde 2 sonuç dönsün diyebilelim ya da limit belirtmeden kütüphanenin default değerleri dönsün diyebilelim.
var dockerImageLimitOption = new Option(
name: "--count",
description: "Search response limit. The default value is null"
);
Örneğin yukarıdaki senaryoda image name girilmeseydi şu şekilde bir çıktı alacaktık

Tabii option tanımladık hemen anında olsun gibi bir şey yok :) option’lar için ne demiştik? Command’lara passlanmalılar. Şimdi gelin bir root command tanımlayalım.
RootCommand Tanımlama
Öncelikle RootCommand nedir onu bilmeliyiz. Bir root command, çalıştırılabilir uygulamanın dosyasının adını belirten komuttur. Bu yazı dotnet üzerine olduğu için dotnet komutu, dotnet.exe’yi belirtir diyebiliriz.
Örneğimiz için RootCommand şöyle olsun;
var rootCommand = new RootCommand("Sample Docker CLI");
rootCommand.AddOption(dockerImageNameOption);
rootCommand.AddOption(dockerImageLimitOption);
Tabiiki bu tarz bir yapı kurulmamalı ama örnek olması açısından veriyorum. Option’larımızı rootCommand olarak ekledik ancak bu option’lara veri geldiğinde bunları yakalayıp işlem yaptırmamız da gerekiyor. Diğer türlü sadece option eklemiş olur ancak handle etmemiş oluruz
rootCommand.SetHandler(async (imageName, imageCount) =>
{
var images = await client.Images.SearchImagesAsync(new Docker.DotNet.Models.ImagesSearchParameters()
{
Term = imageName,
Limit = imageCount
});
foreach (var image in images)
{
Console.WriteLine("----------------");
Console.WriteLine($"Image: {image.Name}");
Console.WriteLine($"Description: {image.Description}");
Console.WriteLine($"Start: {image.StarCount}");
Console.WriteLine($"Official: {(image.IsOfficial ? "Yes" : "No")}");
Console.WriteLine("----------------");
Console.WriteLine(Environment.NewLine);
}
}, dockerImageNameOption, dockerImageLimitOption);
Yukarıdaki kod örneğinde, rootCommand bir variadic yapıya sahiptir. Yani istediğiniz kadar parametre alabiliyor. Tabiiki abartmayın 😛
Aldığı parametrelerin türü ise, option’ların aldığı T türünden.
Daha sonrasında ise şu kodu ekleyerek, CLI tarafında gelen argümanları rootCommand’e paslamamız gerekiyor;
await rootCommand.InvokeAsync(args);
Projemize, launchSettings ile CLI args paslayarak test işlemimizi şöyle yapalım;

Default hiçbir parametrenin girilmediği yani root command’in bir şey parse etmediği durumda eğer uygulamayı çağırır isek şöyle bir çıktımız olacak.

Bu yazı bu kadar. Okuduğunuz için teşekkür ederim. Umarım faydalı olmuştur :)
Örnek GitHub Reposu
GitHub - aligoren/docker-cli-example-with-system-commandline: Docker CLI Example with…