![Latest Version] ![Build status] ![Supported platforms] ![License]



inquire is a library for building interactive prompts on terminals.


Demo

Animated GIF making a demonstration of a questionnaire created with this library. You can replay this recording in your terminal with asciinema play command - asciinema play ./assets/expense_tracker.cast Source

Examples

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

$ cargo run --example expense_tracker --features date

Features

Cross-cutting concerns

There are several features that are shared among different types of prompts. This section will give an overview on each of them.

Validation

Almost all prompts provide an API to set custom validators.

The validators provided to a given prompt are called whenever the user submits their input. These validators vary by prompt type, receiving different types of variables as arguments, such as &str, &[OptionAnswer], or NaiveDate, but their return type are always the same: Result<(), String>.

If the input provided by the user is invalid, your validator should return Ok(()).

If the input is not valid, your validator should return Err(String), where the content of Err is a string whose content will be displayed to the user as an error message. It is recommended that this value gives a helpful feedback to the user, e.g. "This field should contain at least 5 characters".

The validators are typed as a reference to dyn Fn. This allows both functions and closures to be used as validators, but it also means that the functions can not hold any mutable references.

Finally, inquire has a feature called builtin_validators that is included by default. When the feature is on, several built-in validators are exported at the root-level of the library in the form of macros, check their documentation to see more details.

The docs provide full-featured examples.

In the demo on the top of this README, you can see the behavior of an input not passing the requirements in the amount prompt, when the error message "Please type a valid number" is displayed. Full disclosure, this error message was displayed due to a parsing, not validation, error, but the user experience is the same for both cases.

Formatting

Formatting is the process of transforming the user input into a readable output displayed after the user submits their response. By default, this is in some cases just echoing back the input itself, such as in Text prompts. Other prompts have different formatting rules by default, for example DateSelect which formats the selected date into something like "August 5, 2021".

All prompts provide an API to set custom formatters. By setting a formatter, you can customize how the user's response is displayed to them. For example, you might want to format a selected date into a new format such as "05/08/2021".

Custom formatters receive the input as an argument, with varying types such as &str, chrono::NaiveDate, and return a String containing the output to be displayed to the user. Check the docs for specific examples.

In the demo on the top of this README, you can see this behavior in action with the amount (CustomType) prompt, where a custom formatter adds a '$' character preffix to the input.

Parsing

Parsing features are related to two prompts: Confirm and CustomType. They return to you a value (of types bool or any custom type you might want) parsed from the user's text input. In both cases, you can either use default parsers that are already built-in or provide custom ones adhering to the function signatures.

The default bool parser returns true if the input is either "y" or "yes", in a case-insensitive comparison. Similarly, the parser returns false if the input is either "n" or "no".

The default parser for CustomType prompts calls the parse::<T>() method on the input string. This means that if you want to create a CustomType with default settings, the wanted return type must implement the FromStr trait.

In the demo on the top of this README, you can see this behavior in action with the amount (CustomType) prompt.

Filtering

Filtering is applicable to two prompts: Select and MultiSelect. They provide the user the ability to filter the options based on their text input. This is specially useful when there are a lot of options for the user to choose from, allowing them to quickly find their expected options.

Filter functions receive three arguments: the current user input, the option string value and the option index. They must return a bool value indicating whether the option should be part of the results or not.

The default filter function does a naive case-insensitive comparison between the option string value and the current user input, returning true if the option string value contains the user input as a substring.

In the demo on the top of this README, you can see this behavior in action with the account (Select) and tags (MultiSelect) prompts.

Error handling

Error handling when using inquire is pretty simple. Instantiating prompt structs is not fallible by design, in order to avoid requiring chaining of map and and_then methods to subsequent configuration method calls such as with_help_message(). All fallible operations are exposable only when you call prompt() on the instantiated prompt struct.

prompt calls return a Result containing either your expected response value or an Err of type 'InquireError. AnInquireError` has the following variants:

Prompts

Currently, there are 5 different prompt types supported.

Text

Text is the standard kind of prompt you would expect from a library like this one. It displays a message to the user, prompting them to type something back. The user's input is then stored in a String and returned to the prompt caller.

```rust let name = Text::new("What is your name?").prompt();

match name { Ok(name) => println!("Hello {}", name), Err(_) => println!("An error happened when asking for your name, try again later."), } ```

Animated GIF making a demonstration of a simple prompt with Text created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/text_simple.cast

With Text, you can customize several aspects:

Autocomplete

With Text inputs, it is also possible to set-up an auto-completion system to provide a better UX when necessary.

You can set-up a custom Suggester function, which receives the current input as the only argument and should return a vector of strings, all of the suggested values.

The user is then able to select one of them by moving up and down the list, possibly further modifying a selected suggestion.

In the demo on the top of this README, you can see this behavior in action with the payee prompt.

Default behaviors

Default behaviors for each one of Text configuration options:

DateSelect

Animated GIF making a demonstration of a DateSelect prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/date_complete.cast

```rust let date = DateSelect::new("When do you want to travel?") .withdefault(chrono::NaiveDate::fromymd(2021, 8, 1)) .withmindate(chrono::NaiveDate::fromymd(2021, 8, 1)) .withmaxdate(chrono::NaiveDate::fromymd(2021, 12, 31)) .withweekstart(chrono::Weekday::Mon) .withhelpmessage("Possible flights will be displayed according to the selected date") .prompt();

match date { Ok() => println!("No flights available for this date."), Err() => println!("There was an error in the system."), } ```

DateSelect prompts allows user to select a date (time not supported) from an interactive calendar. This prompt is only available when including the date feature in the dependency, as it brings an additional module (chrono) in your dependency tree.

DateSelect prompts provide several options of configuration:

Select

Animated GIF making a demonstration of a simple Select prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/select.cast

```rust let options = vec!["Banana", "Apple", "Strawberry", "Grapes", "Lemon", "Tangerine", "Watermelon", "Orange", "Pear", "Avocado", "Pineapple", ];

let ans = Select::new("What's your favorite fruit?", &options).prompt();

match ans { Ok(choice) => println!("I also love {}!", choice.value), Err(_) => println!("There was an error, please try again"), } ```

The Select prompt is created with a prompt message and a non-empty list of options. It is suitable for when you need the user to select one option among many.

The Select prompt does not support custom validators because of the nature of the prompt. A submission always selects exactly one of the options. If this option was not supposed to be selected or is invalid in some way, it probably should not be included in the options list.

The options are paginated in order to provide a smooth experience to the user, with the default page size being 7. The user can move from the options and the pages will be updated accordingly, including moving from the last to the first options (or vice-versa).

The user can submit their choice by pressing either space or enter.

Like all others, this prompt also allows you to customize several aspects of it:

MultiSelect

Animated GIF making a demonstration of a simple MultiSelect prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/multiselect.cast

The source is too long, find it here.

The MultiSelect prompt is created with a prompt message and a non-empty list of options. It is suitable for when you need the user to select many options (including none if applicable) among a list of them.

The options are paginated in order to provide a smooth experience to the user, with the default page size being 7. The user can move from the options and the pages will be updated accordingly, including moving from the last to the first options (or vice-versa).

The user can pick the current selection by pressing space, cleaning all selections by pressing the left arrow and selecting all options by pressing the right arrow.

Like all others, this prompt also allows you to customize several aspects of it:

Password

Animated GIF making a demonstration of a simple Password prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/password_simple.cast

```rust let name = Password::new("Encryption key:").prompt();

match name { Ok() => println!("This doesn't look like a key."), Err() => println!("An error happened when asking for your key, try again later."), } ```

Password prompts are basically a less-featured version of Text prompts. Differences being:

However, it is still possible to customize error messages, formatters and validators.

CustomType

Animated GIF making a demonstration of a simple CustomType prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/custom_type.cast

```rust let amount = CustomType::::new("How much do you want to donate?") .withformatter(&|i| format!("${:.2}", i)) .witherrormessage("Please type a valid number") .withhelp_message("Type the amount in US dollars using a decimal point as a separator") .prompt();

match amount { Ok() => println!("Thanks a lot for donating that much money!"), Err() => println!("We could not process your donation"), } ```

CustomType prompts are generic prompts suitable for when you need to parse the user input into a specific type, for example an f64 or a rust_decimal, maybe even an uuid.

This prompt has all of the validation, parsing and error handling features built-in to reduce as much boilerplaste as possible from your prompts.

After the user submits, the prompt handler tries to parse the input into the expected type. If the operation succeeds, the value is returned to the prompt caller. If it fails, the message defined in error_message is displayed to the user.

When initializing this prompt via the new() method, some constraints on the return type T are added to make sure we can apply a default parser and formatter to the prompt.

The default parser calls the str.parse method, which means that T must implement the FromStr trait. When the parsing fails for any reason, a default error message "Invalid input" is displayed to the user.

The default formatter simply calls to_string() on the parsed value, which means that T must implement the ToString trait, which normally happens implicitly when you implement the Display trait.

If your type T does not satisfy these constraints, you can always manually instantiate the entire struct yourself like this:

rust let amount_prompt: CustomType<chrono::NaiveDate> = CustomType { message: "When will you travel?", formatter: &|val| val.format("%d/%m/%Y").to_string(), default: None, error_message: "Please type a valid date in the expected format.".into(), help_message: "The date should be in the dd/mm/yyyy format.".into(), parser: &|i| match chrono::NaiveDate::parse_from_str(i, "%d/%m/%Y") { Ok(val) => Ok(val), Err(_) => Err(()), }, };

Confirm

Animated GIF making a demonstration of a simple Confirm prompt created with this library. You can replay this recording in your terminal with asciinema play command using the file ./assets/confirm_simple.cast

```rust let ans = Confirm::new("Do you live in Brazil?") .withdefault(false) .withhelp_message("This data is stored for good reasons") .prompt();

match ans { Ok(true) => println!("That's awesome!"), Ok(false) => println!("That's too bad, I've heard great things about it."), Err(_) => println!("Error with questionnaire, try again later"), } ```

The Confirm is basically a wrapper around the behavior of CustomType prompts, providing a sensible set of defaults to ask for simple true/false questions, such as confirming an action.

The Confirm prompt does not support custom validators because of the nature of the prompt. The user input is always parsed to true or false. If one of the two alternatives is invalid, a Confirm prompt that only allows yes or no answers does not make a lot of sense to me, but if someone provides a clear use-case I will reconsider.

Confirm prompts provide several options of configuration: