Una biblioteca para construir indicaciones interactivas y accesibles en terminales que respaldan las secuencias de escape ANSI.
¡Hola a todos! Finalmente llegué a un acuerdo con el hecho de que ya no puedo dedicar suficiente tiempo para mantener viva esta biblioteca. Este proyecto superó mis expectativas más salvajes y fue una gran experiencia. Si alguien más quiere hacerse cargo del mantenimiento, comuníquese con
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 )
} Se pueden encontrar ejemplos en los examples/ directorio. Ejecutarlos para ver el comportamiento básico:
go run examples/simple.go
go run examples/validation.go Hay dos formas principales de ejecutar indicaciones y comenzar a recopilar información de sus usuarios: Ask y AskOne . La principal diferencia es si está interesado en recopilar una sola información o si tiene una lista de preguntas para hacer cuyas respuestas deben recopilarse en una sola estructura. Para la mayoría de los casos de uso básicos, Ask debe ser suficiente. Sin embargo, para encuestas con una lógica de ramificación complicada, le recomendamos que divida sus preguntas en múltiples llamadas a ambas funciones para satisfacer sus necesidades.
La mayoría de las indicaciones toman una configuración de grano fino a través de campos en las estructuras que instanciona. También es posible cambiar los comportamientos predeterminados de la encuesta pasando AskOpts para Ask o AskOne . Los ejemplos en este documento harán ambos indistintamente:
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 ) Los campos y los valores que provienen de un mensaje Select pueden ser una de las dos cosas diferentes. Si pasa un int el campo tendrá el valor del índice seleccionado. Si en su lugar pasa una cadena, el valor de cadena seleccionado se escribirá en el campo.
El usuario también puede presionar esc para alternar el ciclo de habilidad a través de las opciones con las teclas J y K para hacerlo hacia abajo y hacia arriba, respectivamente.
De manera predeterminada, el indicador de selección se limita a mostrar 7 opciones a la vez y paginará listas de opciones más largas que eso. Esto se puede cambiar de varias maneras:
// 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 ))El texto de descripción opcional se puede utilizar para agregar información adicional a cada opción en la lista en el Solicitud de selección:
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 ) Los campos y los valores que provienen de un aviso MultiSelect pueden ser una de las dos cosas diferentes. Si pasa un int el campo tendrá una porción de los índices seleccionados. Si en su lugar pasa una cadena, se escribirá una porción de los valores de cadena seleccionados en el campo.
El usuario también puede presionar esc para alternar el ciclo de habilidad a través de las opciones con las teclas J y K para hacerlo hacia abajo y hacia arriba, respectivamente.
De manera predeterminada, el indicador multiselecto se limita a mostrar 7 opciones a la vez y paginará listas de opciones más largas que eso. Esto se puede cambiar de varias maneras:
// 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 el editor preferido del usuario (definido por las variables de entorno $ Visual o $ editor) en un archivo temporal. Una vez que el usuario sale de su editor, el contenido del archivo temporal se lee como resultado. Si ninguno de ellos está presente, se usa el bloc de notas (en Windows) o Vim (Linux o Mac).
También puede especificar un patrón para el nombre del archivo temporal. Esto puede ser útil para garantizar que la sintaxis resalte coincida con su USECase.
prompt := & survey. Editor {
Message : "Shell code snippet" ,
FileName : "*.sh" ,
}
survey . AskOne ( prompt , & content )De manera predeterminada, el usuario puede filtrar para opciones en selección y múltiples selecciones escribiendo mientras el mensaje está activo. Esto filtrará todas las opciones que no contienen la cadena escrita en ningún lugar de su nombre, ignorando el caso.
También se puede proporcionar una función de filtro personalizada para cambiar este comportamiento:
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 defecto, el filtro desaparecerá si el usuario selecciona uno de los elementos filtrados. Una vez que el usuario selecciona un elemento, la configuración del filtro desaparece.
Sin embargo, el usuario puede evitar que esto suceda y mantener el filtro activo para selecciones múltiples en un EG 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 validación de las respuestas individuales para una pregunta en particular se puede hacer definiendo un campo Validate en la survey.Question . Esta función toma un tipo de interface{} y devuelve un error para mostrarle al usuario, solicitándolos para otra respuesta. Como de costumbre, los validadores se pueden proporcionar directamente a la solicitud o con 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 viene preempaquetada con algunos validadores para adaptarse a situaciones comunes. Actualmente estos validadores incluyen:
| nombre | tipos válidos | descripción | notas |
|---|---|---|---|
| Requerido | cualquier | Rechaza los valores cero del tipo de respuesta | Los valores booleanos pasan directamente ya que el valor cero (falso) es una respuesta válida |
| MinLength (n) | cadena | Aplica que una respuesta es al menos la longitud dada | |
| Maxlength (n) | cadena | Aplica que una respuesta no es más larga que la longitud dada | |
| Maxitems (n) | [] OppectionAnwer | Aplica que una respuesta no tiene más selecciones de lo indicado | |
| Minitems (n) | [] OppectionAnwer | Aplica que una respuesta no tiene menos selecciones de lo indicado |
Todas las indicaciones tienen un campo Help que se puede definir para proporcionar más información a sus usuarios:
& survey. Input {
Message : "What is your phone number:" ,
Help : "Phone number should include the area code" ,
} De forma predeterminada, los usuarios pueden seleccionar todas las opciones de selección múltiple utilizando la tecla de flecha correcta. Para evitar que los usuarios puedan hacer esto (y eliminar la <right> to all los mensajes de la solicitud), use la opción 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 ()) También de forma predeterminada, los usuarios pueden usar la tecla de flecha izquierda para deseleccionar todas las opciones. Para evitar que los usuarios puedan hacer esto (y eliminar el mensaje <left> to none del mensaje), use la opción 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 ()) En algunas situaciones ? es una respuesta perfectamente válida. Para manejar esto, puede cambiar la runa que busca con 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 ( '^' )) Cambiar los íconos y su color/formato se puede hacer pasando la opción WithIcons . El formato sigue los patrones descritos aquí. Por ejemplo:
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"
}))Los iconos y su texto y formato predeterminados se resumen a continuación:
| nombre | texto | formato | descripción |
|---|---|---|---|
| Error | incógnita | rojo | Antes de un error |
| Ayuda | i | cian | Antes de ayudar a enviar mensajes de texto |
| Pregunta | ? | verde+HB | Antes del mensaje de un aviso |
| Seleccionar | > | verde | Marca el enfoque actual en indicaciones Select y MultiSelect |
| Sin marcar | [] | predeterminado+hb | Marca una opción no seleccionada en un aviso MultiSelect |
| Marcado | [incógnita] | cian+b | Marca una selección elegida en un aviso MultiSelect |
La encuesta asignará respuestas de solicitud a sus tipos personalizados si implementan esta interfaz:
type Settable interface {
WriteAnswer ( field string , value interface {}) error
}Aquí hay un ejemplo de cómo usarlos:
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
) Puede probar las indicaciones interactivas de su programa usando GO-Expect. La biblioteca se puede usar para esperar una coincidencia en Stdout y responder en Stdin. Dado que os.Stdout en un proceso go test no es un TTY, si está manipulando el cursor o utilizando survey , necesitará una forma de interpretar secuencias de escape de Terminal / ANSI para cosas como CursorLocation . vt10x.NewVT10XConsole creará una consola go-expect que también multiplica STDIO a un terminal virtual en memoria.
Para algunos ejemplos, puede ver cualquiera de las pruebas en este repositorio.
survey ?La encuesta tiene como objetivo apoyar a la mayoría de los emuladores terminales; Espera el apoyo a las secuencias de escape ANSI. Esto significa que la lectura de stdin o la escritura de la tubería a stdout no es compatible , y es probable que rompa su aplicación en estas situaciones. Ver #337
Por lo general, cuando escribe CTRL-C, el terminal reconoce esto como el botón Quit y ofrece una señal Sigint al proceso, que lo termina. Sin embargo, la encuesta configura temporalmente el terminal para entregar códigos de control como bytes de entrada ordinarios. Cuando la encuesta lee A ^c Byte (ASCII x03, "Fin del texto"), interrumpe la encuesta actual y devuelve un github.com/AlecAivazis/survey/v2/terminal.InterruptErr de Ask o AskOne . Si desea detener el proceso, maneje el error devuelto en su código:
err := survey . AskOne ( prompt , & myVar )
if err != nil {
if err == terminal. InterruptErr {
log . Fatal ( "interrupted" )
}
...
}