Une bibliothèque pour construire des invites interactives et accessibles sur les terminaux prenant en charge les séquences d'échappement ANSI.
Hé tout le monde! J'ai finalement accepté le fait que je ne peux plus consacrer suffisamment de temps pour maintenir cette bibliothèque en vie. Ce projet a dépassé mes attentes les plus folles et a été une expérience formidable. Si quelqu'un d'autre veut prendre la maintenance, veuillez tendre la main
package main
import (
"fmt"
"github.com/AlecAivazis/survey/v2"
)
// the questions to ask
var qs = [] * survey. Question {
{
Name : "name" ,
Prompt : & survey. Input { Message : "What is your name?" },
Validate : survey . Required ,
Transform : survey . Title ,
},
{
Name : "color" ,
Prompt : & survey. Select {
Message : "Choose a color:" ,
Options : [] string { "red" , "blue" , "green" },
Default : "red" ,
},
},
{
Name : "age" ,
Prompt : & survey. Input { Message : "How old are you?" },
},
}
func main () {
// the answers will be written to this struct
answers := struct {
Name string // survey will match the question and field names
FavoriteColor string `survey:"color"` // or you can tag fields to match a specific name
Age int // if the types don't match, survey will convert it
}{}
// perform the questions
err := survey . Ask ( qs , & answers )
if err != nil {
fmt . Println ( err . Error ())
return
}
fmt . Printf ( "%s chose %s." , answers . Name , answers . FavoriteColor )
} Des exemples peuvent être trouvés dans les examples/ répertoires. Exécutez-les pour voir le comportement de base:
go run examples/simple.go
go run examples/validation.go Il existe deux façons principales d'exécuter des invites et de commencer à collecter des informations auprès de vos utilisateurs: Ask et AskOne . La principale différence est de savoir si vous êtes intéressé à collecter une seule information ou si vous avez une liste de questions pour poser à qui les réponses devraient être collectées dans une seule structure. Pour la plupart des uséases de base, Ask devrait être suffisant. Cependant, pour les enquêtes avec une logique de branchement compliquée, nous vous recommandons de diviser vos questions en plusieurs appels à ces deux fonctions pour répondre à vos besoins.
La plupart des invites prennent une configuration à grain fin via des champs sur les structures que vous instanciez. Il est également possible de modifier les comportements par défaut de l'enquête en passant à AskOpts Ask ou AskOne . Des exemples de ce document feront à la fois de manière interchangeable:
prompt := & Select {
Message : "Choose a color:" ,
Options : [] string { "red" , "blue" , "green" },
// can pass a validator directly
Validate : survey . Required ,
}
// or define a default for the single call to `AskOne`
// the answer will get written to the color variable
survey . AskOne ( prompt , & color , survey . WithValidator ( survey . Required ))
// or define a default for every entry in a list of questions
// the answer will get copied into the matching field of the struct as shown above
survey . Ask ( questions , & answers , survey . WithValidator ( survey . Required )) name := ""
prompt := & survey. Input {
Message : "ping" ,
}
survey . AskOne ( prompt , & name ) file := ""
prompt := & survey. Input {
Message : "inform a file to save:" ,
Suggest : func ( toComplete string ) [] string {
files , _ := filepath . Glob ( toComplete + "*" )
return files
},
}
}
survey . AskOne ( prompt , & file ) text := ""
prompt := & survey. Multiline {
Message : "ping" ,
}
survey . AskOne ( prompt , & text ) password := ""
prompt := & survey. Password {
Message : "Please type your password" ,
}
survey . AskOne ( prompt , & password ) name := false
prompt := & survey. Confirm {
Message : "Do you like pie?" ,
}
survey . AskOne ( prompt , & name ) color := ""
prompt := & survey. Select {
Message : "Choose a color:" ,
Options : [] string { "red" , "blue" , "green" },
}
survey . AskOne ( prompt , & color ) Les champs et les valeurs provenant d'une invite Select peuvent être l'une des deux choses différentes. Si vous passez un int le champ aura la valeur de l'index sélectionné. Si vous passez à la place une chaîne, la valeur de chaîne sélectionnée sera écrite dans le champ.
L'utilisateur peut également appuyer sur esc pour faire basculer le cycle de capacité via les options avec les touches J et K pour faire respectivement et vers le haut.
Par défaut, l'invite de sélection est limitée à afficher 7 options à la fois et paginera les listes d'options plus longtemps que cela. Cela peut être modifié de plusieurs façons:
// as a field on a single select
prompt := & survey. MultiSelect { ... , PageSize : 10 }
// or as an option to Ask or AskOne
survey . AskOne ( prompt , & days , survey . WithPageSize ( 10 ))Le texte de description facultatif peut être utilisé pour ajouter des informations supplémentaires à chaque option répertoriée dans l'invite de sélection:
color := ""
prompt := & survey. Select {
Message : "Choose a color:" ,
Options : [] string { "red" , "blue" , "green" },
Description : func ( value string , index int ) string {
if value == "red" {
return "My favorite color"
}
return ""
},
}
survey . AskOne ( prompt , & color )
// Assuming that the user chose "red - My favorite color":
fmt . Println ( color ) //=> "red"
days := [] string {}
prompt := & survey. MultiSelect {
Message : "What days do you prefer:" ,
Options : [] string { "Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" },
}
survey . AskOne ( prompt , & days ) Les champs et les valeurs provenant d'une invite MultiSelect peuvent être l'une des deux choses différentes. Si vous passez un int le champ aura une tranche des indices sélectionnés. Si vous passez à la place une chaîne, une tranche des valeurs de chaîne sélectionnées sera écrite dans le champ.
L'utilisateur peut également appuyer sur esc pour faire basculer le cycle de capacité via les options avec les touches J et K pour faire respectivement et vers le haut.
Par défaut, l'invite multisecte est limitée à afficher 7 options à la fois et paginera les listes d'options plus longtemps que cela. Cela peut être modifié de plusieurs façons:
// as a field on a single select
prompt := & survey. MultiSelect { ... , PageSize : 10 }
// or as an option to Ask or AskOne
survey . AskOne ( prompt , & days , survey . WithPageSize ( 10 ))Lance l'éditeur préféré de l'utilisateur (défini par les variables d'environnement Visual ou $ editor) sur un fichier temporaire. Une fois que l'utilisateur quitt son éditeur, le contenu du fichier temporaire est lu en conséquence. Si aucun de ceux-ci n'est présent, le bloc-notes (sur Windows) ou VIM (Linux ou Mac) est utilisé.
Vous pouvez également spécifier un modèle pour le nom du fichier temporaire. Cela peut être utile pour garantir la mise en évidence de la syntaxe qui correspond à votre utilisation.
prompt := & survey. Editor {
Message : "Shell code snippet" ,
FileName : "*.sh" ,
}
survey . AskOne ( prompt , & content )Par défaut, l'utilisateur peut filtrer les options dans SELECT et Multiselects en tapant pendant que l'invite est active. Cela filtrera toutes les options qui ne contiennent pas la chaîne tapée nulle part dans leur nom, ignorant le cas.
Une fonction de filtre personnalisée peut également être fournie pour modifier ce comportement:
func myFilter ( filterValue string , optValue string , optIndex int ) bool {
// only include the option if it includes the filter and has length greater than 5
return strings . Contains ( optValue , filterValue ) && len ( optValue ) >= 5
}
// configure it for a specific prompt
& Select {
Message : "Choose a color:" ,
Options : [] string { "red" , "blue" , "green" },
Filter : myFilter ,
}
// or define a default for all of the questions
survey . AskOne ( prompt , & color , survey . WithFilter ( myFilter ))Par défaut, le filtre disparaîtra si l'utilisateur sélectionne l'un des éléments filtrés. Une fois que l'utilisateur sélectionne un élément, le paramètre de filtre a disparu.
Cependant, l'utilisateur peut empêcher que cela ne se produise et maintenir le filtre actif pour plusieurs sélections dans un par exemple multiselect:
// configure it for a specific prompt
& Select {
Message : "Choose a color:" ,
Options : [] string { "light-green" , "green" , "dark-green" , "red" },
KeepFilter : true ,
}
// or define a default for all of the questions
survey . AskOne ( prompt , & color , survey . WithKeepFilter ( true )) La validation des réponses individuelles pour une question particulière peut être effectuée en définissant un champ Validate sur l' survey.Question La question à valider. Cette fonction prend une interface{} Type et renvoie une erreur à afficher à l'utilisateur, en les incitant à une autre réponse. Comme d'habitude, les validateurs peuvent être fournis directement à l'invite ou avec survey.WithValidator .
q := & survey. Question {
Prompt : & survey. Input { Message : "Hello world validation" },
Validate : func ( val interface {}) error {
// since we are validating an Input, the assertion will always succeed
if str , ok := val .( string ) ; ! ok || len ( str ) > 10 {
return errors . New ( "This response cannot be longer than 10 characters." )
}
return nil
},
}
color := ""
prompt := & survey. Input { Message : "Whats your name?" }
// you can pass multiple validators here and survey will make sure each one passes
survey . AskOne ( prompt , & color , survey . WithValidator ( survey . Required )) survey est préemballée avec quelques validateurs pour s'adapter aux situations communes. Actuellement, ces validateurs incluent:
| nom | Types valides | description | notes |
|---|---|---|---|
| Requis | n'importe lequel | Rejette zéro valeurs du type de réponse | Les valeurs booléennes passent directement puisque la valeur zéro (false) est une réponse valide |
| MinLength (n) | chaîne | Impose qu'une réponse est au moins la longueur donnée | |
| MaxLength (n) | chaîne | Impose qu'une réponse n'est pas plus que la longueur donnée | |
| MaxItems (n) | [] OptionSanswer | Implique qu'une réponse n'a plus de sélection | |
| Minitems (N) | [] OptionSanswer | Impose qu'une réponse n'a pas moins de sélection |
Toutes les invites ont un champ Help qui peut être défini pour fournir plus d'informations à vos utilisateurs:
& survey. Input {
Message : "What is your phone number:" ,
Help : "Phone number should include the area code" ,
} Par défaut, les utilisateurs peuvent sélectionner toutes les options multi-sélectionnées à l'aide de la touche flèche droite. Pour empêcher les utilisateurs de pouvoir le faire (et supprimer le <right> to all les messages de l'invite), utilisez l'option WithRemoveSelectAll :
import (
"github.com/AlecAivazis/survey/v2"
)
number := ""
prompt := & survey. Input {
Message : "This question has the select all option removed" ,
}
survey . AskOne ( prompt , & number , survey . WithRemoveSelectAll ()) Par défaut également, les utilisateurs peuvent utiliser la touche flèche gauche pour insélectionner toutes les options. Pour empêcher les utilisateurs de pouvoir le faire (et supprimer le message <left> to none de l'invite), utilisez l'option WithRemoveSelectNone :
import (
"github.com/AlecAivazis/survey/v2"
)
number := ""
prompt := & survey. Input {
Message : "This question has the select all option removed" ,
}
survey . AskOne ( prompt , & number , survey . WithRemoveSelectNone ()) Dans certaines situations , ? est une réponse parfaitement valide. Pour gérer cela, vous pouvez modifier la rune avec laquelle le sondage recherche avec WithHelpInput :
import (
"github.com/AlecAivazis/survey/v2"
)
number := ""
prompt := & survey. Input {
Message : "If you have this need, please give me a reasonable message." ,
Help : "I couldn't come up with one." ,
}
survey . AskOne ( prompt , & number , survey . WithHelpInput ( '^' )) La modification des icônes et de leur couleur / format peut être effectuée en passant l'option WithIcons . Le format suit les modèles décrits ici. Par exemple:
import (
"github.com/AlecAivazis/survey/v2"
)
number := ""
prompt := & survey. Input {
Message : "If you have this need, please give me a reasonable message." ,
Help : "I couldn't come up with one." ,
}
survey . AskOne ( prompt , & number , survey . WithIcons ( func ( icons * survey. IconSet ) {
// you can set any icons
icons . Question . Text = "⁇"
// for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
icons . Question . Format = "yellow+hb"
}))Les icônes et leur texte et leur format par défaut sont résumés ci-dessous:
| nom | texte | format | description |
|---|---|---|---|
| Erreur | X | rouge | Avant une erreur |
| Aide | je | cyan | Avant l'aide du texte |
| Question | ? | vert + hb | Avant le message d'une invite |
| SelectFocus | > | vert | Marque le foyer actuel dans les invites Select et MultiSelect |
| Non marqué | [] | par défaut + HB | Marque une option non sélectionnée dans une invite MultiSelect |
| Marké | [x] | cyan + b | Marque une sélection choisie dans une invite MultiSelect |
L'enquête attribuera des réponses invites à vos types personnalisés s'ils implémentent cette interface:
type Settable interface {
WriteAnswer ( field string , value interface {}) error
}Voici un exemple comment les utiliser:
type MyValue struct {
value string
}
func ( my * MyValue ) WriteAnswer ( name string , value interface {}) error {
my . value = value .( string )
}
myval := MyValue {}
survey . AskOne (
& survey. Input {
Message : "Enter something:" ,
},
& myval
) Vous pouvez tester les invites interactives de votre programme à l'aide de Go-Expect. La bibliothèque peut être utilisée pour s'attendre à un match sur STDOUT et à répondre sur STDIN. Étant donné que os.Stdout dans un processus go test n'est pas un TTY, si vous manipulez le curseur ou utilisez survey , vous aurez besoin d'un moyen d'interpréter les séquences d'échappement Terminal / ANSI pour des choses comme CursorLocation . vt10x.NewVT10XConsole créera une console go-expect qui multiplexe STDIO à un terminal virtuel en mémoire.
Pour quelques exemples, vous pouvez voir n'importe lequel des tests de ce dépôt.
survey ?L'enquête vise à soutenir la plupart des émulateurs terminaux; Il attend le soutien des séquences d'échappement ANSI. Cela signifie que la lecture de Piped Stdin ou l'écriture à STDOUT PIPED n'est pas prise en charge et susceptible de briser votre application dans ces situations. Voir # 337
Habituellement, lorsque vous tapez CTRL-C, le terminal le reconnaît comme le bouton Quit et délivre un signal SIGINT au processus, qui le termine. Cependant, l'enquête configure temporairement le terminal pour fournir des codes de contrôle comme octets d'entrée ordinaires. Lorsque l'enquête lit un octet ^ c (ASCII x03, "Fin du texte"), il interrompt l'enquête actuelle et renvoie un github.com/AlecAivazis/survey/v2/terminal.InterruptErr de Ask ou AskOne . Si vous souhaitez arrêter le processus, gérez l'erreur renvoyée dans votre code:
err := survey . AskOne ( prompt , & myVar )
if err != nil {
if err == terminal. InterruptErr {
log . Fatal ( "interrupted" )
}
...
}