برنامه نویسی

درخواست‌های تودرتو در 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
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه به شرح زیر است
تو در تو

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا