Modül dersleri (4/5)
Mini proje: bayraklı bir CLI
Küçük bir deyimsel CLI oluşturmak için önceki modüllerin yapı taşlarını bir araya getirelim: flag paketiyle bayrak ayrıştırma, konumsal argümanların işlenmesi, os.Stderr üzerindeki hatalar ve doğru çıkış kodu.
flag paketi
package main
import (
"flag"
"fmt"
"os"
)
func main() {
n := flag.Int("n", 1, "number of repetitions")
upper := flag.Bool("u", false, "print in uppercase")
flag.Parse()
args := flag.Args() // remaining positional arguments
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
os.Exit(2)
}
text := args[0]
if *upper {
text = strings.ToUpper(text)
}
for i := 0; i < *n; i++ {
fmt.Println(text)
}
}Anahtar noktalar:
flag.Int,flag.String,flag.Bool,flag.Duration(vb.) bir işaretçi döndürür:*n,*upperkullanırsınız.flag.Parse()değerleri okunmadan önce veflag.Args()'dan önce BİR KEZ çağrılmalıdır.flag.Args()konumsal bağımsız değişkenleri döndürür (--'den sonraki veya son işaretten sonrakiler).- Kabul edilen formlar:
-n 3,-n=3,--n=3. Boolean'lar:-u(doğru),-u=false.
Geleneksel çıkış kodları
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)Kabuklar ve komut dosyaları bu kodları bekler: bunları tutarlı bir şekilde kullanın. Asla ana olmayan bir goroutine'den os.Exit ile çıkmayın: log temizleme dahil her defer atlanır.
Stderr ve Stdout
Eski ve kutsal Unix kuralı:
os.Stdout→ yararlı çıktı, yönlendirilebilir (cli | grep ...).os.Stderr→ teşhisler, hatalar, ilerleme çubukları, kullanım.
fmt.Println("result: 42") // stdout
fmt.Fprintln(os.Stderr, "error: ...") // stderrBu şekilde cli 2>/dev/null yalnızca sonuçları gösterir, cli >out.txt yalnızca sonucu yönlendirir ve hatalar görünür kalır.
log ve fmt
- Kullanıcı çıkışı için
fmt. - Teşhis için
log: bir zaman damgası ekler, varsayılan olarakos.Stderr'ye yazar,log.Fatal(günlükler +os.Exit(1)) velog.Paniciçerir.
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1Modern uygulamalarda (Go 1.21+), yapılandırılmış günlük kaydı (JSON veya metin, düzeyler, yazılan alanlar) için log/slog kullanın.
Mimari: main'yı mantıktan ayırın
func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args []string, stdout, stderr io.Writer) error {
// here use a new flag.FlagSet instead of the global flag.CommandLine
fs := flag.NewFlagSet("echo", flag.ContinueOnError)
fs.SetOutput(stderr)
n := fs.Int("n", 1, "repetitions")
if err := fs.Parse(args); err != nil {
return err
}
// ...
return nil
}Avantajları:
- Test edilebilir:
bytes.Buffer'yi stdout/stderr olarak iletirsiniz ve çıktıyı doğrularsınız. - Küresel durum yok: her
FlagSetizole edilmiştir. mainküçük kalır: IO + delegelerini hazırlar.
Alt komutlar
Alt komutlara sahip CLI'ler için (mycli serve, mycli migrate), harici bağımlılığı olmayan bir şema:
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
switch os.Args[1] {
case "serve":
serveCmd(os.Args[2:])
case "migrate":
migrateCmd(os.Args[2:])
default:
usage()
os.Exit(2)
}
}Her alt komutun kendi flag.NewFlagSet'si vardır. Daha büyük projeler için cobra veya urfave/cli gibi kütüphaneler bu şemayı otomatik olarak oluşturulan yardımla yapılandırır.
Egzersizler
Flag.Parse() işlevinin varsayılan 1 değerine göre tanımlanması ve 'oluşturulmasının' açıklanması, flag.Parse() işlevinin tanımlanması ve değerin damgalanması.
3 denemeden sonra çözüm mevcut
Eğer bayrak konumunda bir argüman bulunmuyorsa.Ayrıştırın, Stderr'a 'manca argomento' damgasını koyun ve os.Exit(1) ile sonlandırın.
3 denemeden sonra çözüm mevcut
Unix dostu bir CLI'de hata mesajı ve kullanımıyla ilgili kaliteli bir akış var mı?
fmt.Fprintln(???, "uso: prog ...")