لماذا Go هي اللغة المثالية لأدوات CLI؟
\n\nتخيل: تحتاج إلى كتابة أداة سيتم تشغيلها في وحدة التحكم، ومعالجة الملفات، والاتصال بـ API، أو ببساطة أتمتة المهام الروتينية. يمكنك اختيار Python أو Node.js أو Bash. ولكن إذا كانت السرعة وحجم الملف الثنائي وسهولة التوزيع أمورًا حاسمة - فإن اختيارك هو أدوات CLI بلغة Go.
\n\nيتم تجميع Go في ملف ثنائي ثابت واحد. لا تبعيات، ولا أسئلة مثل "هل لديك Python 3.8 مثبت؟". قم بالتنزيل والتشغيل. هذا يجعل أدوات Golang CLI المعيار الفعلي في عالم DevOps (تذكر Docker و Kubernetes و Hugo - كلها مكتوبة بلغة Go).
\n\nفي هذه المقالة، لن نتحدث فقط عن النظرية. سنقوم بإنشاء أداة وحدة تحكم بلغة Go حقيقية من الصفر، والتي ستقوم بتحليل JSON والعمل مع HTTP وعرض جداول جميلة. ستتعلم كيفية هيكلة المشروع ومعالجة الأخطاء والتجميع لأي منصة.
\n\nإعداد البيئة وهيكل المشروع
\n\nقبل كتابة الكود، تأكد من تثبيت Go (الإصدار 1.21+). يمكنك التحقق ببساطة:
\n\ngo version\n\nلنقم بإنشاء دليل لمشروعنا. سنسمي الأداة json2table - ستقوم بقراءة JSON من stdin أو ملف وعرض البيانات في شكل جدول.
mkdir json2table\ncd json2table\ngo mod init github.com/your-username/json2table\n\nسيكون هيكل المشروع كلاسيكيًا لـ التطوير بلغة Go:
\n\n- \n
main.go- نقطة الدخول \n internal/- المنطق الداخلي (التحليل، العرض) \n cmd/- كود معالجة الأوامر (إذا استخدمنا Cobra) \n
سنبدأ ببساطة، بدون أطر عمل، ثم سنضيف الأعلام والمساعدة.
\n\nكتابة جوهر الأداة: تحليل JSON وعرض الجدول
\n\nقم بإنشاء ملف internal/parser.go. هنا سنقوم بتحليل JSON وتحويله إلى هيكل.
package internal\n\nimport (\n "encoding/json"\n "fmt"\n "io"\n)\n\n// Record يمثل صفًا واحدًا في الجدول\ntype Record map[string]interface{}\n\n// ParseJSON يقرأ JSON من القارئ ويعيد شريحة من السجلات\nfunc ParseJSON(r io.Reader) ([]Record, error) {\n var data []Record\n decoder := json.NewDecoder(r)\n if err := decoder.Decode(&data); err != nil {\n return nil, fmt.Errorf("خطأ في تحليل JSON: %w", err)\n }\n return data, nil\n}\n\nالآن لننشئ internal/table.go للإخراج المنسق:
package internal\n\nimport (\n "fmt"\n "strings"\n "text/tabwriter"\n "os"\n)\n\n// PrintTable يعرض السجلات في شكل جدول مع محاذاة\nfunc PrintTable(records []Record) {\n if len(records) == 0 {\n fmt.Println("لا توجد بيانات للعرض")\n return\n }\n\n // نجمع جميع المفاتيح الفريدة (عناوين الأعمدة)\n headers := make([]string, 0)\n seen := make(map[string]bool)\n for _, rec := range records {\n for key := range rec {\n if !seen[key] {\n headers = append(headers, key)\n seen[key] = true\n }\n }\n }\n\n // نستخدم tabwriter للإخراج الجميل\n w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\n // العنوان\n fmt.Fprintln(w, strings.Join(headers, "\\t"))\n\n // الفاصل\n sep := make([]string, len(headers))\n for i, h := range headers {\n sep[i] = strings.Repeat("-", len(h))\n }\n fmt.Fprintln(w, strings.Join(sep, "\\t"))\n\n // البيانات\n for _, rec := range records {\n row := make([]string, len(headers))\n for i, h := range headers {\n if val, ok := rec[h]; ok {\n row[i] = fmt.Sprintf("%v", val)\n } else {\n row[i] = ""\n }\n }\n fmt.Fprintln(w, strings.Join(row, "\\t"))\n }\n\n w.Flush()\n}\n\n