درخواستهای تودرتو در Go با استفاده از promptui

من اخیراً با استفاده از ابزار Cobra بر روی یک ابزار CLI نوشته شده در Go کار می کردم و یک مورد استفاده داشتم که در آن به یک دستور تودرتو برای یکی از دستورات نیاز داشتم. من از promptui برای درخواستها استفاده میکردم و راه سادهای برای انجام این کار پیدا نکردم. این پست کوتاه نحوه ایجاد یک اعلان تودرتو با استفاده از آن را نشان می دهد promptui
. کد تکمیل شده را می توانید در اینجا پیدا کنید.
ابتدا باید یک پروژه Go خالی ایجاد کنیم. ما آن را صدا خواهیم کرد nested-prompt
:
$ mkdir nested-prompt && cd nested-prompt
$ go mod init github.com/Thwani47/nested-prompt
سپس آن را نصب می کنیم cobra
، cobra-cli
، و promptui
بسته ها:
$ go get -u github.com/spf13/cobra@latest
$ go install github.com/spf13/cobra-cli@latest
$ go get -u github.com/manifoldco/promptui
ما می توانیم یک برنامه CLI جدید را با استفاده از cobra-cli
و یک دستور به CLI خود اضافه کنید
$ cobra-cli init # initializes a new CLI application
$ cobra-cli add config # adds a new command to the CLI named 'config'
ما می توانیم پاکسازی کنیم cmd/config.go
فایل و تمام نظرات را حذف کنید. باید به این صورت باشد:
// cmd/config.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Configure settings for the application",
Long: `Configure settings for the application`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("config called")
},
}
func init() {
rootCmd.AddCommand(configCmd)
}
ابتدا باید یک نوع سفارشی برای درخواست خود ایجاد کنیم. ما این کار را با تعریف a انجام می دهیم promptItem
به شرح زیر است
type PromptType int
const (
TextPrompt PromptType = 0
PasswordPrompt PromptType = 1
SelectPrompt PromptType = 2
)
type promptItem struct {
ID string
Label string
Value string
SelectOptions []string
promptType PromptType
}
این PromptType
enum به ما امکان می دهد انواع مختلفی از ورودی را از دستورات خود جمع آوری کنیم، می توانیم متن یا مقادیر حساس مانند رمز عبور یا کلیدهای API را از کاربر درخواست کنیم یا از کاربر بخواهیم از لیست مقادیر تعریف شده انتخاب کند.
سپس الف را تعریف می کنیم promptInput
عملکردی که از کاربر درخواست می کند. تابع مقدار رشته وارد شده توسط کاربر یا یک خطا را در صورت عدم موفقیت درخواست برمی گرداند.
func promptInput(item promptItem) (string, error) {
prompt := promptui.Prompt{
Label: item.Label,
HideEntered: true,
}
if item.promptType == PasswordPrompt {
prompt.Mask = '*'
}
res, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return "", err
}
return res, nil
}
سپس الف را تعریف می کنیم promptSelect
عملکردی که به کاربر امکان می دهد از لیستی از گزینه ها انتخاب کند. تابع مقدار رشته انتخاب شده توسط کاربر یا یک خطا را در صورت عدم موفقیت درخواست برمی گرداند.
func promptSelect(item selectItem) (string, error) {
prompt := promptui.Select{
Label: item.Label,
Items: item.SelectValues,
HideSelected: true,
}
_, result, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return "", err
}
return result, nil
}
برای شبیه سازی یک اعلان تو در تو، a ایجاد می کنیم promptNested
تابعی که به ما امکان می دهد از کاربر یک مقدار را درخواست کنیم و تا زمانی که کاربر انتخاب کند، فرمان فعال می ماند "Done"
. تابع یک مقدار بولی برمی گرداند که نشان می دهد درخواست موفقیت آمیز بوده است.
نظرات در تابع توضیح می دهد که هر بلوک اصلی کد مسئول چه چیزی است
func promptNested(promptLabel string, startingIndex int, items []*promptItem) bool {
// Add a "Done" option to the prompt if it does not exist
doneID := "Done"
if len(items) > 0 && items[0].ID != doneID {
items = append([]*promptItem{{ID: doneID, Label: "Done"}}, items...)
}
templates := &promptui.SelectTemplates{
Label: "{{ . }}?",
Active: "\U0001F336 {{ .Label | cyan }}",
Inactive: "{{ .Label | cyan }}",
Selected: "\U0001F336 {{ .Label | red | cyan }}",
}
prompt := promptui.Select{
Label: promptLabel,
Items: items,
Templates: templates,
Size: 3,
HideSelected: true,
CursorPos: startingIndex, // Set the cursor to the last selected item
}
idx, _, err := prompt.Run()
if err != nil {
fmt.Printf("Error occurred when running prompt: %v\n", err)
return false
}
selectedItem := items[idx]
// if the user selects "Done", return true and exit from the function
if selectedItem.ID == doneID {
return true
}
var promptResponse string
// if the prompt type is Text or Password, prompt the user for input
if selectedItem.promptType == TextPrompt || selectedItem.promptType == PasswordPrompt {
promptResponse, err = promptInput(*selectedItem)
if err != nil {
fmt.Printf("Error occurred when running prompt: %v\n", err)
return false
}
items[idx].Value = promptResponse
}
// if the prompt type is Select, prompt the user to select from a list of options
if selectedItem.promptType == SelectPrompt {
promptResponse, err = promptSelect(*selectedItem)
if err != nil {
fmt.Printf("Error occurred when running prompt: %v\n", err)
return false
}
items[idx].Value = promptResponse
}
if err != nil {
fmt.Printf("Error occurred when running prompt: %v\n", err)
return false
}
// recursively call the promptNested function to allow the user to select another option
return promptNested(idx, items)
}
اکنون ما تمام روش های مورد نیاز خود را داریم و باید آنها را آزمایش کنیم. درون Run
عملکرد از configCmd
دستور، ما یک لیست از promptItem
و تماس بگیرید promptNested
تابعی برای درخواست ورودی از کاربر. این Run
تابع باید به شکل زیر باشد:
// create a list of prompt items
items := []*promptItem{
{
ID: "APIKey",
Label: "API Key",
promptType: PasswordPrompt,
},
{
ID: "Theme",
Label: "Theme",
promptType: SelectPrompt,
SelectOptions: []string{"Dark", "Light"},
},
{
ID: "Language",
Label: "Preferred Language",
promptType: SelectPrompt,
SelectOptions: []string{"English", "Spanish", "French", "German", "Chinese", "Japanese"},
},
}
// set the starting index to 0 to start at the first item in the list
promptNested("Configuration Items", 0, items)
for _, v := range items {
fmt.Printf("Saving configuration (%s) with value (%s)...\n", v.ID, v.Value)
}
به صورت زیر اپلیکیشن را بسازید و تست کنید
$ go build .
$ ./nested-prompt config
نتیجه به شرح زیر است