بناء تطبيقات CLI في Go باستخدام Cobra و Viper
تطوير واجهة سطر الأوامر (CLI) في Go باستخدام إطارات Cobra و Viper
واجهات برمجة التطبيقات (CLI) ضرورية للتطوير، ومشرفو النظام، والمهنيين في DevOps. لقد أصبحت مكتبتان لغة Go هي المعيار الافتراضي لتطوير واجهات برمجة التطبيقات في Go: Cobra لهيكل الأوامر و Viper لإدارة التكوين.
لقد ظهرت Go كلغة ممتازة لبناء أدوات CLI بسبب أداءها، ونشرها البسيط، ودعمها المتعدد للمنصات.

لماذا تختار Go لتطبيقات CLI
تقدم Go مزايا مقنعة لتطوير CLI:
- توزيع ملف واحد: لا حاجة لاعتماديات التشغيل أو أدوات إدارة الحزم
- التنفيذ السريع: يوفر التجميع الأصلي أداءً ممتازًا
- دعم منصات متعددة: سهولة التجميع لـ Linux، macOS، Windows، وغيرها
- مكتبة قياسية قوية: أدوات غنية لـ I/O الملفات، والشبكات، ومعالجة النصوص
- التوافقيات: goroutines مدمجة للعمليات المتوازية
- النوعية الثابتة: اكتشاف الأخطاء في وقت التجميع
تطبيقات CLI الشهيرة المبنية باستخدام Go تشمل Docker، Kubernetes (kubectl)، Hugo، Terraform، وGitHub CLI. إذا كنت جديدًا في Go أو تحتاج إلى مرجع سريع، تحقق من ورقة Go المختصرة الخاصة بنا لمعرفة أهمية Go والأنماط.
مقدمة إلى Cobra
Cobra هي مكتبة توفر واجهة بسيطة لإنشاء تطبيقات CLI قوية حديثة. تم إنشاؤها بواسطة Steve Francia (spf13)، نفس الكاتب الذي وراء Hugo و Viper، يستخدم Cobra في العديد من المشاريع الشهيرة لـ Go.
ميزات Cobra الرئيسية
هيكل الأوامر: يطبق Cobra نمط الأوامر، مما يسمح لك بإنشاء تطبيقات تحتوي على أوامر و الأوامر الفرعية (مثل git commit أو docker run).
إدارة الأعلام: أعلام محلية ومستمرة مع تحليل تلقائي وتحويل النوع.
المساعدة التلقائية: توليد نص المساعدة والمعلومات حول الاستخدام تلقائيًا.
الاقتراحات الذكية: تقدم اقتراحات عند إدخال المستخدم أخطاء (“هل تقصد ‘status’؟”).
إكمال Shell: توليد نصوص إكمال لـ bash، zsh، fish، وPowerShell.
إخراج مرن: يعمل بسلاسة مع المُشكّلين المخصصة وأساليب الإخراج.
مقدمة إلى Viper
Viper هي حل كامل لإدارة التكوين في تطبيقات Go، تم تصميمها للعمل بسلاسة مع Cobra. تتعامل مع التكوين من مصادر متعددة مع ترتيب واضح للإعطاء الأولوية.
ميزات Viper الرئيسية
مصادر التكوين المتعددة:
- ملفات التكوين (JSON، YAML، TOML، HCL، INI، envfile، Java properties)
- المتغيرات البيئية
- أعلام سطر الأوامر
- أنظمة التكوين البعيدة (etcd، Consul)
- القيم الافتراضية
ترتيب أولوية التكوين:
- المكالمات الصريحة لـ Set
- أعلام سطر الأوامر
- المتغيرات البيئية
- ملف التكوين
- مخزن المفاتيح/القيم
- القيم الافتراضية
مراقبة الحالة الحية: مراقبة ملفات التكوين وإعادة تحميلها تلقائيًا عند حدوث تغييرات.
تحويل النوع: تحويل تلقائي إلى أنواع Go المختلفة (السلسلة، العدد، المنطقي، المدة، إلخ).
البدء: التثبيت
أولاً، قم بتهيئة وحدة Go الجديدة وثبيت المكتبات:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
بشكل اختياري، قم بتثبيت مولد Cobra CLI لبناء الهيكل:
go install github.com/spf13/cobra-cli@latest
بناء تطبيق CLI أول
لنقم ببناء مثال عملي: أداة إدارة المهام CLI مع دعم التكوين.
هيكل المشروع
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
نقطة الدخول الرئيسية
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
الأوامر الأساسية مع دمج Viper
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "mytasks",
Short: "أداة إدارة المهام البسيطة",
Long: `MyTasks هي أداة إدارة المهام من سطر الأوامر التي تساعدك على تنظيم
مهامك اليومية بسهولة. تم بناؤها باستخدام Cobra و Viper.`,
}
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", "",
"ملف التكوين (الافتراضي هو $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"موقع ملف قاعدة البيانات")
viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("db"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
viper.AddConfigPath(home)
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName(".mytasks")
}
viper.SetEnvPrefix("MYTASKS")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("يتم استخدام ملف التكوين:", viper.ConfigFileUsed())
}
}
إضافة الأوامر الفرعية
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [وصف المهمة]",
Short: "إضافة مهمة جديدة",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("إضافة مهمة: %s\n", task)
fmt.Printf("الأولوية: %s\n", priority)
fmt.Printf("قاعدة البيانات: %s\n", db)
// هنا ستقوم بتنفيذ تخزين المهام الفعلي
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"الأولوية (منخفضة، متوسطة، عالية)")
}
أمر القائمة
// cmd/list.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var showCompleted bool
var listCmd = &cobra.Command{
Use: "list",
Short: "عرض جميع المهام",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("عرض المهام من: %s\n", db)
fmt.Printf("عرض المهام المكتملة: %v\n", showCompleted)
// هنا ستقوم بتنفيذ عرض المهام الفعلي
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"عرض المهام المكتملة")
}
تنفيذ تخزين البيانات
لتطبيق إدارة المهام من سطر الأوامر في الإنتاج، ستحتاج إلى تنفيذ تخزين البيانات الفعلي. بينما نستخدم هنا مسار ملف قاعدة البيانات البسيط، لديك خيارات متعددة لتخزين البيانات:
- SQLite: قاعدة بيانات خفيفة بدون خادم مثالية لتطبيقات سطر الأوامر
- PostgreSQL/MySQL: قواعد بيانات كاملة لتطبيقات أكثر تعقيدًا
- ملفات JSON/YAML: تخزين بسيط بناءً على الملفات للاحتياجات الخفيفة
- قواعد بيانات مدمجة: BoltDB، BadgerDB لتخزين المفاتيح/القيم
إذا كنت تعمل مع قواعد بيانات علائقية مثل PostgreSQL، فسترغب في استخدام ORM أو مُنشئ الاستعلامات. لمراجعة شاملة للمكتبات الخاصة بـ Go للقواعد البيانات، راجع دليلنا حول مقارنة ORMs لـ Go لـ PostgreSQL: GORM مقابل Ent مقابل Bun مقابل sqlc.
التكوين المتقدم مع Viper
مثال على ملف التكوين
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
قراءة التكوين المُضمن
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("الإشعارات مفعلة: %v\n", enabled)
fmt.Printf("الصوت مفعل: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
المتغيرات البيئية
Viper تقرأ تلقائيًا المتغيرات البيئية مع المُقدمة المُكوّنة:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
إعادة تحميل التكوين الحية
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("تم تغيير ملف التكوين:", e.Name)
// إعادة تحميل المكونات المعتمدة على التكوين
})
}
أفضل الممارسات
1. استخدام مولد Cobra للحصول على اتساق
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. تنظيم الأوامر في ملفات منفصلة
احتفظ بكل أمر في ملف منفصل تحت الدليل cmd/ للحفاظ على سهولة الصيانة.
3. الاستفادة من الأعلام المستمرة
استخدم الأعلام المستمرة للخيارات التي تنطبق على جميع الأوامر الفرعية:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"نوع الإخراج (نص، json، yaml)")
4. تنفيذ معالجة الأخطاء المناسبة
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "تطبيقي",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("فشل في تنفيذ شيء ما: %w", err)
}
return nil
},
}
5. تقديم نصوص مساعدة مفيدة
var cmd = &cobra.Command{
Use: "deploy [environment]",
Short: "نشر التطبيق إلى البيئة المحددة",
Long: `نشر البناء ونشر التطبيق إلى البيئة
المحددة. البيئات المدعومة هي:
- التطوير (dev)
- التصحيح
- الإنتاج (prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. ضبط القيم الافتراضية المناسبة
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. التحقق من صحة التكوين
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("منفذ غير صحيح: %d", port)
}
return nil
}
اختبار تطبيقات CLI
اختبار الأوامر
// cmd/root_test.go
package cmd
import (
"bytes"
"testing"
)
func TestRootCommand(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"--help"})
if err := cmd.Execute(); err != nil {
t.Fatalf("فشل تنفيذ الأمر: %v", err)
}
}
اختبار مع تكوينات مختلفة
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// قم بتشغيل اختباراتك
viper.Reset() // التنظيف
}
إنشاء نصوص إكمال Shell
يمكن لـ Cobra إنشاء نصوص إكمال لعدد من الأنظمة:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "إنشاء نص إكمال",
Long: `لتحميل الإكمال:
Bash:
$ source <(mytasks completion bash)
Zsh:
$ source <(mytasks completion zsh)
Fish:
$ mytasks completion fish | source
PowerShell:
PS> mytasks completion powershell | Out-String | Invoke-Expression
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
بناء وتوزيع
بناء لمنصات متعددة
# Linux
GOOS=linux GOARCH=amd64 go build -o mytasks-linux-amd64
# macOS
GOOS=darwin GOARCH=amd64 go build -o mytasks-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o mytasks-darwin-arm64
# Windows
GOOS=windows GOARCH=amd64 go build -o mytasks-windows-amd64.exe
تقليل حجم الملف
go build -ldflags="-s -w" -o mytasks
إضافة معلومات الإصدار
// cmd/version.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "عرض معلومات الإصدار",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("الإصدار: %s\n", Version)
fmt.Printf("الالتزام: %s\n", Commit)
fmt.Printf("البناء: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
بناء مع معلومات الإصدار:
go build -ldflags="-X 'mytasks/cmd.Version=1.0.0' \
-X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
-X 'mytasks/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
-o mytasks
أمثلة واقعية
أدوات مفتوحة المصدر الشهيرة المبنية باستخدام Cobra و Viper:
- kubectl: أداة سطر الأوامر لـ Kubernetes
- Hugo: مولّد المواقع الثابتة
- GitHub CLI (gh): أداة سطر الأوامر الرسمية لـ GitHub
- Docker CLI: إدارة الحاويات
- Helm: مُدار لـ Kubernetes
- Skaffold: أداة لتدفق تطوير Kubernetes
- Cobra CLI: تلقائي - Cobra يستخدم نفسه!
بالإضافة إلى أدوات DevOps التقليدية، تصل قدرات Go في تطبيقات سطر الأوامر إلى تطبيقات الذكاء الاصطناعي والتعلم الآلي. إذا كنت مهتمًا ببناء أدوات سطر الأوامر تتفاعل مع نماذج لغات كبيرة، تحقق من دليلنا حول تقييد نماذج LLM باستخدام الإخراج المهيكل باستخدام Ollama و Go.
مصادر مفيدة
- مستودع GitHub لـ Cobra
- مستودع GitHub لـ Viper
- دليل المستخدم لـ Cobra
- مستندات Viper
- أفضل الممارسات لتطبيقات CLI في Go
الخاتمة
معًا، تقدم Cobra و Viper أساسًا قويًا لبناء تطبيقات CLI احترافية في Go. تتعامل Cobra مع هيكل الأوامر، وتحليل الأعلام، وتكوين المساعدة، بينما تتعامل Viper مع إدارة التكوين من مصادر متعددة مع أولوية ذكية.
هذا التكامل يسمح لك بإنشاء أدوات CLI التي تكون:
- سهلة الاستخدام مع أوامر بديهية ومساعدة تلقائية
- مرنة مع مصادر تكوين متعددة
- احترافية مع إكمال Shell ومعالجة الأخطاء المناسبة
- قابلة للصيانة مع تنظيم الكود النظيف
- قابلة للنقل عبر منصات مختلفة
سواء كنت تبني أدوات مطورين، أو أدوات نظام، أو تلقين DevOps، فإن Cobra و Viper توفران الأساس الصلب الذي تحتاجه لبناء تطبيقات CLI التي سيحبها المستخدمون.