flag dan cobra

Pembangunan CLI dan parsing argumen.

Daftar isi

Program baris perintah (command line) menerima argumen dari terminal, dan Go menyediakan paket flag di pustaka standar untuk mengurainya menjadi opsi bernama. Topik ini membahas cara mendeklarasikan opsi dengan nilai default, menyusun subperintah melalui flag.NewFlagSet, serta membandingkannya dengan cobra, yaitu pustaka eksternal dari spf13 yang dipakai untuk program CLI bertingkat. Contohnya berupa dua alat kecil, yaitu alat salam dan alat catatan, yang dijalankan tanpa argumen maupun dengan argumen eksplisit sehingga perilakunya terlihat lengkap. Setelah membacanya, antarmuka baris perintah dapat dibangun dengan parsing argumen yang benar dan pesan pemakaian yang informatif.

Parsing argumen dengan paket flag#

Paket flag mendeklarasikan opsi melalui fungsi seperti flag.String dan flag.Int, dan masing-masing mengembalikan pointer ke variabel yang akan diisi. Urutan panggilannya adalah mendeklarasikan seluruh opsi, memanggil flag.Parse untuk mengurai os.Args, kemudian membaca nilai melalui pointer tersebut. Setiap opsi memiliki nilai default, sehingga program tetap dapat dijalankan ketika opsi itu tidak diberikan, dan paket ini otomatis menyediakan opsi -h yang mencetak daftar opsi beserta teks penjelasannya. Program berikut membentuk alat salam dengan dua opsi dan mencetak hasilnya menggunakan nilai default.

go alat-salam.go
package main

import (
	"flag"
	"fmt"
	"strings"
)

func main() {
	nama := flag.String("nama", "Dunia", "nama penerima salam")
	ulang := flag.Int("ulang", 1, "banyaknya pengulangan")
	flag.Parse()

	fmt.Print(strings.Repeat(fmt.Sprintf("Halo, %s!\n", *nama), *ulang))
}
text Keluaran
Halo, Dunia!

Keluaran di atas berasal dari eksekusi tanpa argumen, sehingga kedua opsi memakai nilai default Dunia dan 1. Pointer nama dan ulang diisi oleh flag.Parse berdasarkan argumen baris perintah, dan strings.Repeat mengulang salam sesuai nilai ulang. Urutan opsi pada baris perintah bebas karena flag.Parse mencocokkan namanya dan bukan posisinya, sedangkan teks penjelasan pada setiap deklarasi menjadi isi halaman bantuan opsi -h. Pemanggilan dengan argumen eksplisit mengubah kedua nilai tersebut tanpa perubahan apa pun pada kode, sebagaimana diperlihatkan blok terminal pada bagian berikutnya.

Subperintah dengan FlagSet#

Program CLI sering memakai subperintah (subcommand), yaitu kata pertama yang menentukan perilaku program, seperti go mod init yang memilih subperintah mod lalu init. Paket flag mendukung pola ini melalui flag.NewFlagSet, yaitu kumpulan opsi mandiri yang diparsing dari potongan os.Args milik subperintahnya. Argumen posisi, yakni argumen yang tidak diawali tanda hubung, tetap dapat dibaca melalui metode Args milik FlagSet tersebut. Program berikut membentuk alat catatan dengan dua subperintah, yaitu salam dengan opsi dan tambah dengan argumen posisi berupa bilangan.

go alat-catatan.go
package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("pemakaian: catat <subperintah> [opsi]")
		fmt.Println("subperintah: salam, tambah")
		return
	}

	switch os.Args[1] {
	case "salam":
		fs := flag.NewFlagSet("salam", flag.ExitOnError)
		nama := fs.String("nama", "Dunia", "nama penerima salam")
		fs.Parse(os.Args[2:])
		fmt.Printf("Halo, %s!\n", *nama)
	case "tambah":
		fs := flag.NewFlagSet("tambah", flag.ExitOnError)
		fs.Parse(os.Args[2:])
		total := 0
		for _, argumen := range fs.Args() {
			n, err := strconv.Atoi(argumen)
			if err != nil {
				fmt.Println("bukan bilangan bulat:", argumen)
				os.Exit(1)
			}
			total += n
		}
		fmt.Println("jumlah:", total)
	default:
		fmt.Println("subperintah tidak dikenal:", os.Args[1])
		os.Exit(1)
	}
}
text Keluaran
pemakaian: catat <subperintah> [opsi]
subperintah: salam, tambah

Keluaran di atas berasal dari eksekusi tanpa argumen, sehingga program mencetak petunjuk pemakaiannya dan berhenti dengan status nol. Struktur switch pada os.Args[1] memilih subperintahnya, dan setiap cabang membuat FlagSet sendiri supaya opsi antarsubperintah tidak saling bercampur. Subperintah tambah mengurai argumen posisinya satu per satu dengan strconv.Atoi, sehingga argumen yang bukan bilangan menghentikan program dengan status keluar 1. Kedua alat dijalankan dari terminal dengan argumen eksplisit sebagaimana berikut.

bash
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gopustaka/flag
cp alat-salam.go alat-catatan.go /tmp/gopustaka/flag/
cd /tmp/gopustaka/flag

# jalankan alat salam dengan kedua opsinya
go run alat-salam.go -nama=Ali -ulang=2

# jalankan subperintah salam dengan opsinya
go run alat-catatan.go salam -nama=Budi

# jalankan subperintah tambah dengan argumen posisi
go run alat-catatan.go tambah 2 3 5
text Keluaran
Halo, Ali!
Halo, Ali!
Halo, Budi!
jumlah: 10

cobra untuk CLI bertingkat#

Paket cobra dari github.com/spf13/cobra adalah pustaka eksternal yang menyusun program CLI di atas pohon perintah, yaitu perintah utama beserta subperintah yang dapat bersarang beberapa tingkat. Setiap perintah dideklarasikan sebagai struct cobra.Command yang memuat nama, teks penjelasan singkat, daftar subperintahnya, dan fungsi yang dijalankan ketika perintah tersebut dipilih. Paket ini juga menghasilkan halaman bantuan otomatis untuk setiap tingkat perintah dan menambahkan pelengkapan tab (shell completion) untuk shell bash, zsh, serta fish. Contoh berikut memperlihatkan bentuk deklarasinya yang tidak dijalankan karena pustakanya berada di luar pustaka standar.

go Deklarasi subperintah pada cobra
// tidak-dijalankan
var salamCmd = &cobra.Command{
	Use:   "salam",
	Short: "Mencetak salam untuk satu nama",
	RunE: func(cmd *cobra.Command, args []string) error {
		nama, _ := cmd.Flags().GetString("nama")
		fmt.Printf("Halo, %s!\n", nama)
		return nil
	},
}

func init() {
	salamCmd.Flags().String("nama", "Dunia", "nama penerima salam")
	rootCmd.AddCommand(salamCmd)
}

Fragmen di atas memperlihatkan perbedaan pendekatannya terhadap paket flag. Field Use menentukan nama subperintah pada baris perintah, field Short menjadi teks pada halaman bantuan, dan RunE menampung logikanya beserta error yang dapat dikembalikan. Pendaftaran opsi tetap menyerupai flag.String, karena cobra membangun penanganan opsinya di atas pustaka pflag, yaitu pustaka terpisah yang tata polanya mengikuti paket flag. Sebagai perbandingan, alat salam dua opsi pada bagian awal topik ini cukup ditulis dengan flag saja, sedangkan cobra memberi keuntungan ketika jumlah subperintah dan tingkat sarangnya bertambah.

Kesalahan umum#

  • Lupa memanggil flag.Parse. Deklarasi opsi hanya mendaftarkannya, sehingga tanpa Parse seluruh pointer tetap berisi nilai default dan argumen baris perintah diabaikan. Perbaikannya, panggil flag.Parse atau fs.Parse tepat setelah seluruh opsi dideklarasikan.
  • Membaca argumen posisi dari flag.Args padahal memakai FlagSet sendiri. Fungsi global flag.Args mengembalikan argumen milik CommandLine, bukan milik FlagSet subperintah, sehingga hasilnya selalu kosong. Perbaikannya, gunakan metode Args pada FlagSet yang diparsing.
  • Mencampur opsi dua FlagSet dalam satu pemanggilan. FlagSet bersifat mandiri, sehingga opsi yang dideklarasikan pada subperintah lain tidak dikenal dan parsingnya gagal. Perbaikannya, deklarasikan ulang opsi yang dibutuhkan pada setiap FlagSet.
  • Mengasumsikan argumen posisi berada sebelum opsi. Paket flag menghentikan parsing pada argumen posisi pertama, sehingga opsi setelahnya masuk ke Args. Perbaikannya, tempatkan opsi sebelum argumen posisi atau terima urutan itu sebagai konvensi programnya.
  • Menghentikan program dari dalam subperintah tanpa status keluar. return biasa melaporkan keberhasilan meskipun pekerjaannya gagal, sehingga skrip pemanggil tidak mengetahuinya. Perbaikannya, panggil os.Exit(1) atau kembalikan error sesuai pola programnya.

Ringkasan#

  • Paket flag mendeklarasikan opsi melalui flag.String dan flag.Int yang mengembalikan pointer, kemudian mengurainya dengan flag.Parse.
  • Nilai default membuat program tetap dapat dijalankan tanpa argumen, dan opsi -h tersedia secara otomatis beserta teks penjelasannya.
  • flag.NewFlagSet membangun subperintah dengan kumpulan opsi mandiri, sedangkan argumen posisinya dibaca melalui metode Args.
  • cobra adalah pustaka eksternal untuk CLI bertingkat dengan pohon perintah, bantuan otomatis, dan pelengkapan tab, sedangkan flag cukup untuk alat yang sederhana.

Referensi#