Convert Figma logo to code with AI

AlecAivazis logosurvey

A golang library for building interactive and accessible prompts with full support for windows and posix terminals.

4,082
349
4,082
75

Top Related Projects

1,707

Advanced ANSI style & color support for your terminal applications

5,338

TUI components for Bubble Tea 🫧

Interactive prompt for command-line applications

Readline is a pure go(golang) implementation for GNU-Readline kind library

Quick Overview

Survey is a Go library for building interactive and customizable command-line interfaces. It provides a simple and elegant way to prompt users for input, offering various question types and customization options to create engaging CLI experiences.

Pros

  • Easy to use with a clean and intuitive API
  • Supports multiple question types (text, password, confirm, select, multi-select)
  • Highly customizable with options for validation, default values, and help text
  • Cross-platform compatibility

Cons

  • Limited to command-line interfaces, not suitable for GUI applications
  • May require additional effort for complex input scenarios
  • Documentation could be more comprehensive for advanced use cases

Code Examples

  1. Simple text input:
name := ""
prompt := &survey.Input{
    Message: "What is your name?",
}
survey.AskOne(prompt, &name)
  1. Password input with validation:
password := ""
prompt := &survey.Password{
    Message: "Please enter your password:",
}
survey.AskOne(prompt, &password, survey.WithValidator(survey.Required))
  1. Multi-select question:
colors := []string{}
prompt := &survey.MultiSelect{
    Message: "Choose your favorite colors:",
    Options: []string{"Red", "Blue", "Green", "Yellow", "Purple"},
}
survey.AskOne(prompt, &colors)

Getting Started

To use Survey in your Go project, first install it:

go get -u github.com/AlecAivazis/survey/v2

Then, import and use it in your code:

package main

import (
    "fmt"
    "github.com/AlecAivazis/survey/v2"
)

func main() {
    name := ""
    prompt := &survey.Input{
        Message: "What is your name?",
    }
    survey.AskOne(prompt, &name)
    fmt.Printf("Hello, %s!\n", name)
}

This example prompts the user for their name and then greets them. Run the program to see the interactive prompt in action.

Competitor Comparisons

1,707

Advanced ANSI style & color support for your terminal applications

Pros of termenv

  • Focuses on terminal styling and color manipulation
  • Provides cross-platform color support and ANSI escape sequences
  • Offers a more lightweight and focused API for terminal output styling

Cons of termenv

  • Limited to terminal output styling, lacking input handling capabilities
  • Does not provide survey or prompt functionality
  • May require additional libraries for complex CLI applications

Code Comparison

termenv:

import "github.com/muesli/termenv"

p := termenv.ColorProfile()
s := termenv.String("Hello, World!").Foreground(p.Color("204"))
fmt.Println(s)

survey:

import "github.com/AlecAivazis/survey/v2"

name := ""
prompt := &survey.Input{Message: "What is your name?"}
survey.AskOne(prompt, &name)
fmt.Printf("Hello, %s!\n", name)

Summary

termenv is a specialized library for terminal styling and color manipulation, while survey focuses on interactive command-line prompts and surveys. termenv excels in cross-platform color support and ANSI escape sequences, making it ideal for creating visually appealing terminal output. However, it lacks the input handling and survey functionality provided by survey. Choose termenv for projects requiring advanced terminal styling, and survey for interactive CLI applications with user input requirements.

5,338

TUI components for Bubble Tea 🫧

Pros of Bubbles

  • More comprehensive UI toolkit with various components (spinners, progress bars, etc.)
  • Built-in support for styling and theming
  • Designed for building complex TUIs with multiple interactive elements

Cons of Bubbles

  • Steeper learning curve due to more complex API
  • May be overkill for simple CLI applications
  • Less focused on survey-specific functionality

Code Comparison

Survey:

prompt := &survey.Input{
    Message: "Enter your name:",
}
survey.AskOne(prompt, &name)

Bubbles:

m := model{textInput: textinput.New()}
m.textInput.Placeholder = "Enter your name"
p := tea.NewProgram(m)
p.Run()

Summary

Survey is more focused on creating simple CLI prompts and surveys, while Bubbles provides a comprehensive toolkit for building complex terminal user interfaces. Survey is easier to use for basic input gathering, while Bubbles offers more flexibility and customization options for advanced TUI applications. The choice between the two depends on the complexity of the desired user interface and the specific requirements of the project.

Interactive prompt for command-line applications

Pros of promptui

  • More extensive and customizable UI elements, including spinners and progress bars
  • Built-in support for password input and masking
  • Better handling of terminal resizing and window management

Cons of promptui

  • Less straightforward API, requiring more setup for basic prompts
  • Fewer built-in validation options compared to survey
  • Limited support for complex, nested question structures

Code Comparison

survey:

prompt := &survey.Input{
    Message: "Enter your name:",
}
survey.AskOne(prompt, &name)

promptui:

prompt := promptui.Prompt{
    Label: "Enter your name",
}
result, err := prompt.Run()

Both libraries provide similar functionality for basic prompts, but promptui requires more explicit error handling. survey offers a more concise API for simple use cases, while promptui provides more granular control over the prompt behavior and appearance.

promptui excels in creating rich, interactive CLI experiences with its advanced UI elements. However, survey's simpler API and built-in validation make it more suitable for quickly implementing straightforward user input scenarios.

Choose promptui for complex, visually appealing CLI applications, and survey for rapid development of simple command-line tools with user input requirements.

Readline is a pure go(golang) implementation for GNU-Readline kind library

Pros of readline

  • Lower-level library, offering more fine-grained control over input handling
  • Provides cross-platform support for line editing and history features
  • Lightweight and focused on a single task, making it easier to integrate into existing projects

Cons of readline

  • Requires more code to implement complex input scenarios
  • Less user-friendly for creating multi-question surveys or complex prompts
  • Limited built-in support for input validation and formatting

Code Comparison

readline:

rl, err := readline.New("> ")
defer rl.Close()
for {
    line, err := rl.Readline()
    // Process input
}

survey:

prompt := &survey.Input{
    Message: "Enter your name:",
}
survey.AskOne(prompt, &name)

Summary

readline is a lower-level library focused on line editing and history features, offering more control but requiring more implementation effort. survey provides a higher-level abstraction for creating interactive prompts and surveys, with built-in support for various question types and input validation. Choose readline for fine-grained control over input handling, and survey for quickly creating user-friendly interactive prompts with minimal code.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Survey

GoDoc

A library for building interactive and accessible prompts on terminals supporting ANSI escape sequences.

⚠️ This project is no longer maintained. For an alternative, please check out: https://github.com/charmbracelet/bubbletea ⚠️

Hey everyone! I finally came to terms with the fact that I can no longer dedicate enough time to keep this library alive. This project outgrew my wildest expectations and was such a great experience. If someone else wants to take over maintainence, please reach out

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)
}

Examples

Examples can be found in the examples/ directory. Run them to see basic behavior:

go run examples/simple.go
go run examples/validation.go

Running the Prompts

There are two primary ways to execute prompts and start collecting information from your users: Ask and AskOne. The primary difference is whether you are interested in collecting a single piece of information or if you have a list of questions to ask whose answers should be collected in a single struct. For most basic usecases, Ask should be enough. However, for surveys with complicated branching logic, we recommend that you break out your questions into multiple calls to both of these functions to fit your needs.

Configuring the Prompts

Most prompts take fine-grained configuration through fields on the structs you instantiate. It is also possible to change survey's default behaviors by passing AskOpts to either Ask or AskOne. Examples in this document will do both interchangeably:

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))

Prompts

Input

name := ""
prompt := &survey.Input{
    Message: "ping",
}
survey.AskOne(prompt, &name)

Suggestion Options

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)

Multiline

text := ""
prompt := &survey.Multiline{
    Message: "ping",
}
survey.AskOne(prompt, &text)

Password

password := ""
prompt := &survey.Password{
    Message: "Please type your password",
}
survey.AskOne(prompt, &password)

Confirm

name := false
prompt := &survey.Confirm{
    Message: "Do you like pie?",
}
survey.AskOne(prompt, &name)

Select

color := ""
prompt := &survey.Select{
    Message: "Choose a color:",
    Options: []string{"red", "blue", "green"},
}
survey.AskOne(prompt, &color)

Fields and values that come from a Select prompt can be one of two different things. If you pass an int the field will have the value of the selected index. If you instead pass a string, the string value selected will be written to the field.

The user can also press esc to toggle the ability cycle through the options with the j and k keys to do down and up respectively.

By default, the select prompt is limited to showing 7 options at a time and will paginate lists of options longer than that. This can be changed a number of ways:

// 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))

Select options description

The optional description text can be used to add extra information to each option listed in the select prompt:

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"

MultiSelect

Example

days := []string{}
prompt := &survey.MultiSelect{
    Message: "What days do you prefer:",
    Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
}
survey.AskOne(prompt, &days)

Fields and values that come from a MultiSelect prompt can be one of two different things. If you pass an int the field will have a slice of the selected indices. If you instead pass a string, a slice of the string values selected will be written to the field.

The user can also press esc to toggle the ability cycle through the options with the j and k keys to do down and up respectively.

By default, the MultiSelect prompt is limited to showing 7 options at a time and will paginate lists of options longer than that. This can be changed a number of ways:

// 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))

Editor

Launches the user's preferred editor (defined by the $VISUAL or $EDITOR environment variables) on a temporary file. Once the user exits their editor, the contents of the temporary file are read in as the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used.

You can also specify a pattern for the name of the temporary file. This can be useful for ensuring syntax highlighting matches your usecase.

prompt := &survey.Editor{
    Message: "Shell code snippet",
    FileName: "*.sh",
}

survey.AskOne(prompt, &content)

Filtering Options

By default, the user can filter for options in Select and MultiSelects by typing while the prompt is active. This will filter out all options that don't contain the typed string anywhere in their name, ignoring case.

A custom filter function can also be provided to change this behavior:

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))

Keeping the filter active

By default the filter will disappear if the user selects one of the filtered elements. Once the user selects one element the filter setting is gone.

However the user can prevent this from happening and keep the filter active for multiple selections in a e.g. 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))

Validation

Validating individual responses for a particular question can be done by defining a Validate field on the survey.Question to be validated. This function takes an interface{} type and returns an error to show to the user, prompting them for another response. Like usual, validators can be provided directly to the prompt or with 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))

Built-in Validators

survey comes prepackaged with a few validators to fit common situations. Currently these validators include:

namevalid typesdescriptionnotes
RequiredanyRejects zero values of the response typeBoolean values pass straight through since the zero value (false) is a valid response
MinLength(n)stringEnforces that a response is at least the given length
MaxLength(n)stringEnforces that a response is no longer than the given length
MaxItems(n)[]OptionAnswerEnforces that a response has no more selections of the indicated
MinItems(n)[]OptionAnswerEnforces that a response has no less selections of the indicated

Help Text

All of the prompts have a Help field which can be defined to provide more information to your users:

&survey.Input{
    Message: "What is your phone number:",
    Help:    "Phone number should include the area code",
}

Removing the "Select All" and "Select None" options

By default, users can select all of the multi-select options using the right arrow key. To prevent users from being able to do this (and remove the <right> to all message from the prompt), use the 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())

Also by default, users can use the left arrow key to unselect all of the options. To prevent users from being able to do this (and remove the <left> to none message from the prompt), use the 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())

Changing the input rune

In some situations, ? is a perfectly valid response. To handle this, you can change the rune that survey looks for with 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('^'))

Changing the Icons

Changing the icons and their color/format can be done by passing the WithIcons option. The format follows the patterns outlined here. For example:

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"
}))

The icons and their default text and format are summarized below:

nametextformatdescription
ErrorXredBefore an error
HelpicyanBefore help text
Question?green+hbBefore the message of a prompt
SelectFocus>greenMarks the current focus in Select and MultiSelect prompts
UnmarkedOption[ ]default+hbMarks an unselected option in a MultiSelect prompt
MarkedOption[x]cyan+bMarks a chosen selection in a MultiSelect prompt

Custom Types

survey will assign prompt answers to your custom types if they implement this interface:

type Settable interface {
    WriteAnswer(field string, value interface{}) error
}

Here is an example how to use them:

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
)

Testing

You can test your program's interactive prompts using go-expect. The library can be used to expect a match on stdout and respond on stdin. Since os.Stdout in a go test process is not a TTY, if you are manipulating the cursor or using survey, you will need a way to interpret terminal / ANSI escape sequences for things like CursorLocation. vt10x.NewVT10XConsole will create a go-expect console that also multiplexes stdio to an in-memory virtual terminal.

For some examples, you can see any of the tests in this repo.

FAQ

What kinds of IO are supported by survey?

survey aims to support most terminal emulators; it expects support for ANSI escape sequences. This means that reading from piped stdin or writing to piped stdout is not supported, and likely to break your application in these situations. See #337

Why isn't Ctrl-C working?

Ordinarily, when you type Ctrl-C, the terminal recognizes this as the QUIT button and delivers a SIGINT signal to the process, which terminates it. However, Survey temporarily configures the terminal to deliver control codes as ordinary input bytes. When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the current survey and returns a github.com/AlecAivazis/survey/v2/terminal.InterruptErr from Ask or AskOne. If you want to stop the process, handle the returned error in your code:

err := survey.AskOne(prompt, &myVar)
if err != nil {
	if err == terminal.InterruptErr {
		log.Fatal("interrupted")
	}
	...
}