Go dili, sadeliği, performansı ve güçlü standart kütüphanesiyle CLI (Command Line Interface) araçları geliştirmek için birinci tercih haline geldi. Docker, Kubernetes ve GitHub CLI gibi endüstri standardı araçların tamamı Go ile yazılmış. Bu derste Cobra framework'üyle sıfırdan profesyonel bir CLI uygulaması inşa edeceksin.
Neden Go ile CLI?
// PLAINTEXT //
Go CLI Avantajları:
✓ Tek binary — kurulum gerekmez, kopyala-çalıştır
✓ Çapraz derleme — linux/windows/mac tek komutla
✓ Düşük bellek — Python/Node'dan 10x verimli
✓ Hızlı başlangıç — 5ms vs Python'un 150ms'si
✓ Cobra + Viper — endüstri standardı CLI çatısıProje Kurulumu
// BASH //
mkdir codeforge-cli && cd codeforge-cli
go mod init github.com/kullanici/codeforge-cli
# Cobra bağımlılığı
go get github.com/spf13/cobra@latest
go get github.com/spf13/viper@latestCobra ile Temel Yapı
Cobra, komutları hiyerarşik olarak organize eder. Her cobra.Command birbirinden bağımsız, test edilebilir bir birimdir.
// GO //
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
verbose bool
)
// rootCmd — tüm alt komutların üstündeki ana komut
var rootCmd = &cobra.Command{
Use: "codeforge",
Short: "CodeForge CLI — geliştirici araç seti",
Long: `CodeForge CLI, proje iskeletleri oluşturmak,
kod şablonları üretmek ve geliştirme iş akışını
hızlandırmak için tasarlanmış bir araçtır.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config dosyası (varsayılan: $HOME/.codeforge.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "ayrıntılı çıktı")
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".codeforge")
}
viper.AutomaticEnv()
viper.ReadInConfig()
}Alt Komut Oluşturma: generate
// GO //
// cmd/generate.go
package cmd
import (
"fmt"
"os"
"strings"
"text/template"
"github.com/spf13/cobra"
)
var (
projectName string
projectType string
withDocker bool
)
var generateCmd = &cobra.Command{
Use: "generate [proje-adı]",
Short: "Yeni proje iskeleti oluştur",
Long: "Seçilen şablona göre proje yapısını ve dosyalarını oluşturur.",
Aliases: []string{"gen", "g"},
Args: cobra.ExactArgs(1), // tam olarak 1 argüman zorunlu
RunE: runGenerate, // hata döndüren versiyonu kullan
}
func init() {
rootCmd.AddCommand(generateCmd)
generateCmd.Flags().StringVarP(&projectType, "type", "t", "api", "proje tipi: api|web|cli")
generateCmd.Flags().BoolVar(&withDocker, "docker", false, "Dockerfile ve compose ekle")
// Tamamlama fonksiyonu — tab ile öneri
generateCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"api", "web", "cli"}, cobra.ShellCompDirectiveNoFileComp
})
}
func runGenerate(cmd *cobra.Command, args []string) error {
projectName = args[0]
// Proje adı doğrulama
if strings.ContainsAny(projectName, " /\\") {
return fmt.Errorf("geçersiz proje adı: %q — boşluk ve / \\ karakterleri kullanılamaz", projectName)
}
fmt.Printf("🔨 Proje oluşturuluyor: %s (%s)\n", projectName, projectType)
if err := createProjectStructure(projectName, projectType); err != nil {
return fmt.Errorf("proje yapısı oluşturulamadı: %w", err)
}
if withDocker {
if err := createDockerFiles(projectName); err != nil {
return fmt.Errorf("Docker dosyaları oluşturulamadı: %w", err)
}
fmt.Println("🐳 Docker dosyaları eklendi")
}
fmt.Printf("\n✅ Proje hazır: ./%s\n", projectName)
fmt.Printf(" cd %s && go run main.go\n", projectName)
return nil
}
// Şablon tabanlı dosya oluşturma
var apiMainTmpl = template.Must(template.New("main").Parse(`package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(` + "`" + `{"status":"ok","app":"{{.Name}}"}` + "`" + `))
})
log.Println("{{.Name}} başlatılıyor: :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
`))
func createProjectStructure(name, typ string) error {
dirs := []string{name, name + "/cmd", name + "/internal"}
for _, d := range dirs {
if err := os.MkdirAll(d, 0755); err != nil {
return err
}
}
f, err := os.Create(name + "/main.go")
if err != nil {
return err
}
defer f.Close()
return apiMainTmpl.Execute(f, map[string]string{"Name": name})
}
func createDockerFiles(name string) error {
dockerfile := `FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o /bin/app ./...
FROM scratch
COPY --from=builder /bin/app /app
ENTRYPOINT ["/app"]
`
return os.WriteFile(name+"/Dockerfile", []byte(dockerfile), 0644)
}İnteraktif Prompt: init Komutu
// GO //
// cmd/init.go — kullanıcıdan girdi alan komut
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Mevcut dizinde proje başlat",
RunE: runInit,
}
func init() {
rootCmd.AddCommand(initCmd)
}
func prompt(reader *bufio.Reader, soru, varsayilan string) string {
if varsayilan != "" {
fmt.Printf("%s [%s]: ", soru, varsayilan)
} else {
fmt.Printf("%s: ", soru)
}
cevap, _ := reader.ReadString('\n')
cevap = strings.TrimSpace(cevap)
if cevap == "" {
return varsayilan
}
return cevap
}
func runInit(cmd *cobra.Command, args []string) error {
reader := bufio.NewReader(os.Stdin)
fmt.Println("⚡ CodeForge Proje Başlatıcı\n")
ad := prompt(reader, "Proje adı", "my-project")
dil := prompt(reader, "Dil (go/node/python)", "go")
port := prompt(reader, "Port", "8080")
fmt.Printf("\nProje yapılandırması:\n")
fmt.Printf(" Ad: %s\n", ad)
fmt.Printf(" Dil: %s\n", dil)
fmt.Printf(" Port: %s\n", port)
return nil
}Flags: Zorunlu ve Kalıcı
// GO //
// Zorunlu flag — eksikse komut çalışmaz
generateCmd.MarkFlagRequired("type")
// Kalıcı flag — tüm alt komutlar için geçerli
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// Karşılıklı dışlayan flags
generateCmd.MarkFlagsMutuallyExclusive("docker", "no-docker")
// Birlikte zorunlu flags
generateCmd.MarkFlagsRequiredTogether("user", "password")Çapraz Derleme
// MAKEFILE //
# Makefile
build-all:
GOOS=linux GOARCH=amd64 go build -o dist/codeforge-linux-amd64 .
GOOS=darwin GOARCH=arm64 go build -o dist/codeforge-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -o dist/codeforge-windows.exe .
# Sürüm bilgisini binary'ye göm
VERSION ?= $(shell git describe --tags --always)
build:
go build -ldflags "-X main.Version=$(VERSION)" -o dist/codeforge .Shell Tamamlama
// BASH //
# Bash tamamlama dosyası oluştur
codeforge completion bash > /etc/bash_completion.d/codeforge
# Zsh
codeforge completion zsh > ~/.zsh/completions/_codeforge
# Fish
codeforge completion fish > ~/.config/fish/completions/codeforge.fishTest Yazımı
// GO //
// cmd/generate_test.go
package cmd
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGenerateCommand(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
errMsg string
}{
{"geçerli proje", []string{"my-api", "--type", "api"}, false, ""},
{"boşluk içeren ad", []string{"my api"}, true, "geçersiz proje adı"},
{"geçersiz tip", []string{"proj", "--type", "ruby"}, true, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.SetArgs(append([]string{"generate"}, tt.args...))
err := rootCmd.Execute()
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}Özet
Cobra ile Go CLI araçları geliştirmek hem hızlı hem de sürdürülebilir. RunE (hata döndüren), Args doğrulama, MarkFlagRequired ve RegisterFlagCompletionFunc kombinasyonu production kalitesinde CLI araçları için standart yaklaşımdır. Binary boyutu küçük, performans yüksek, dağıtım kolaydır.