Uma biblioteca para a criação de instruções interativas e acessíveis em terminais que suportam sequências de fuga da ANSI.
Ei pessoal! Finalmente cheguei a um acordo com o fato de que não posso mais dedicar tempo suficiente para manter essa biblioteca viva. Este projeto superou minhas expectativas mais loucas e foi uma experiência tão boa. Se alguém quiser assumir a manutenção, entre em contato
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 )
} Exemplos podem ser encontrados nos examples/ diretório. Execute -os para ver o comportamento básico:
go run examples/simple.go
go run examples/validation.go Existem duas maneiras principais de executar prompts e começar a coletar informações de seus usuários: Ask and AskOne . A principal diferença é se você está interessado em coletar uma única informação ou se tem uma lista de perguntas para fazer cujas respostas devem ser coletadas em uma única estrutura. Para a maioria das casas de USECAS, Ask , deve ser suficiente. No entanto, para pesquisas com lógica de ramificação complicada, recomendamos que você divida suas perguntas em várias chamadas para essas duas funções para atender às suas necessidades.
A maioria dos avisos toma configuração de grão fino através dos campos nas estruturas que você instanciam. Também é possível alterar os comportamentos padrão da Pesquisa, passando AskOpts para Ask ou AskOne . Exemplos neste documento farão os dois intercambiáveis:
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 ) Campos e valores provenientes de um prompt Select podem ser uma das duas coisas diferentes. Se você passar por um int o campo terá o valor do índice selecionado. Se você passar uma string, o valor da string selecionado será gravado no campo.
O usuário também pode pressionar esc para alternar o ciclo de capacidade através das opções com as teclas J e K para fazer e subir, respectivamente.
Por padrão, o prompt de seleção é limitado a mostrar 7 opções por vez e pagará listas de opções por mais tempo. Isso pode ser alterado de várias maneiras:
// 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 ))O texto de descrição opcional pode ser usado para adicionar informações extras a cada opção listada no prompt de selecionar:
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 ) Campos e valores provenientes de um prompt MultiSelect podem ser uma das duas coisas diferentes. Se você passar int o campo terá uma fatia dos índices selecionados. Se você passar uma string, uma fatia dos valores de string selecionada será gravada no campo.
O usuário também pode pressionar esc para alternar o ciclo de capacidade através das opções com as teclas J e K para fazer e subir, respectivamente.
Por padrão, o prompt de múltiplas selecionados é limitado a mostrar 7 opções por vez e pagina listas de opções por mais tempo do que isso. Isso pode ser alterado de várias maneiras:
// 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 ))Inicia o editor preferido do usuário (definido pelas variáveis de ambiente $ visual ou $ editor) em um arquivo temporário. Depois que o usuário sai do editor, o conteúdo do arquivo temporário é lido como resultado. Se nenhum desses estiver presente, o bloco de notas (no Windows) ou o VIM (Linux ou Mac) for usado.
Você também pode especificar um padrão para o nome do arquivo temporário. Isso pode ser útil para garantir que a sintaxe de destaque corresponda à sua USECASE.
prompt := & survey. Editor {
Message : "Shell code snippet" ,
FileName : "*.sh" ,
}
survey . AskOne ( prompt , & content )Por padrão, o usuário pode filtrar opções em seleção e multisselectos digitando enquanto o prompt está ativo. Isso filtrará todas as opções que não contêm a string digitada em nenhum lugar em seu nome, ignorando o caso.
Uma função de filtro personalizada também pode ser fornecida para alterar este comportamento:
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 ))Por padrão, o filtro desaparecerá se o usuário selecionar um dos elementos filtrados. Depois que o usuário selecionar um elemento, a configuração do filtro se foi.
No entanto, o usuário pode impedir que isso aconteça e manter o filtro ativo para várias seleções em um por exemplo, multisselect:
// 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 )) A validação de respostas individuais para uma pergunta específica pode ser feita definindo um campo Validate na survey.Question a ser validada. Esta função pega uma interface{} tipo e retorna um erro a ser exibido ao usuário, solicitando a outra resposta. Como de costume, os validadores podem ser fornecidos diretamente ao prompt ou com 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 vem pré -embalada com alguns validadores para atender a situações comuns. Atualmente, esses validadores incluem:
| nome | tipos válidos | descrição | Notas |
|---|---|---|---|
| Obrigatório | qualquer | Rejeita zero valores do tipo de resposta | Os valores booleanos passam direto, pois o valor zero (false) é uma resposta válida |
| MinLength (n) | corda | Aplica que uma resposta é pelo menos o comprimento dado | |
| Maxlength (n) | corda | Aplica que uma resposta não é mais do que o dado | |
| Maxitems (n) | [] OptionAnswer | Aplica que uma resposta não tenha mais seleções do indicado | |
| Minitems (n) | [] OptionAnswer | Aplica que uma resposta não tenha menos seleções do indicado |
Todos os avisos têm um campo Help que pode ser definido para fornecer mais informações aos seus usuários:
& survey. Input {
Message : "What is your phone number:" ,
Help : "Phone number should include the area code" ,
} Por padrão, os usuários podem selecionar todas as opções de seleção múltipla usando a tecla de seta direita. Para impedir que os usuários possam fazer isso (e remover o <right> to all a mensagem do prompt), use a opção 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 ()) Além disso, por padrão, os usuários podem usar a tecla de seta esquerda para desfazer todas as opções. Para impedir que os usuários possam fazer isso (e remover a <left> to none mensagem do prompt), use a opção 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 ()) Em algumas situações ? é uma resposta perfeitamente válida. Para lidar com isso, você pode alterar a runa que a pesquisa procura com 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 ( '^' )) Alterar os ícones e sua cor/formato pode ser feita passando a opção WithIcons . O formato segue os padrões descritos aqui. Por exemplo:
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"
}))Os ícones e seu texto e formato padrão estão resumidos abaixo:
| nome | texto | formatar | descrição |
|---|---|---|---|
| Erro | X | vermelho | Antes de um erro |
| Ajuda | eu | ciano | Antes do texto de ajuda |
| Pergunta | ? | verde+hb | Antes da mensagem de um rápido |
| SelectFocus | > | verde | Marca o foco atual em avisos Select e MultiSelect |
| Não marcado | [] | padrão+hb | Marca uma opção não selecionada em um prompt MultiSelect |
| MarkedOption | [x] | ciano+b | Marca uma seleção escolhida em um prompt MultiSelect |
A pesquisa atribuirá respostas rápidas aos seus tipos personalizados se eles implementarem esta interface:
type Settable interface {
WriteAnswer ( field string , value interface {}) error
}Aqui está um exemplo de como usá -los:
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
) Você pode testar os avisos interativos do seu programa usando o Go-Expect. A biblioteca pode ser usada para esperar uma correspondência no stdout e responder no stdin. Como os.Stdout em um processo go test não é um TTY, se você estiver manipulando o cursor ou usando survey , precisará de uma maneira de interpretar sequências de escape terminal / ANSI para coisas como CursorLocation . vt10x.NewVT10XConsole criará um console go-expect que também multiplex STDIO em um terminal virtual na memória.
Para alguns exemplos, você pode ver qualquer um dos testes neste repositório.
survey ?A pesquisa visa apoiar a maioria dos emuladores terminais; Ele espera suporte para sequências de fuga da ANSI. Isso significa que a leitura de stdin canalizada ou escrita para o stdout canalizada não é suportada e provavelmente quebrará seu aplicativo nessas situações. Veja #337
Normalmente, quando você digita CTRL-C, o terminal reconhece isso como o botão Quit e fornece um sinal de sigint ao processo, que o encerra. No entanto, a pesquisa configura temporariamente o terminal para fornecer códigos de controle como bytes de entrada comuns. Quando a pesquisa lê um byte ^c (ASCII x03, "Fim do texto"), interrompe a pesquisa atual e retorna um github.com/AlecAivazis/survey/v2/terminal.InterruptErr de Ask ou AskOne . Se você deseja interromper o processo, lide com o erro retornado em seu código:
err := survey . AskOne ( prompt , & myVar )
if err != nil {
if err == terminal. InterruptErr {
log . Fatal ( "interrupted" )
}
...
}