TTY::Prompt
A beautiful and powerful interactive command line prompt.
TTY::Prompt provides independent prompt component for TTY toolkit.
Features
- Number of prompt types for gathering user input
- A robust API for validating complex inputs
- User friendly error feedback
- Intuitive DSL for creating complex menus
- Ability to page long menus
- Support for Linux, OS X, FreeBSD and Windows systems
Windows support
tty-prompt
works across all Unix and Windows systems in the "best possible" way. On Windows, it uses Win32 API in place of terminal device to provide matching functionality.
Since Unix terminals provide richer set of features than Windows PowerShell consoles, expect to have a better experience on Unix-like platform.
Some features like select
or multi_select
menus may not work on Windows when run from Git Bash. See GitHub suggested fixes.
For Windows, consider installing ConEmu, cmder or PowerCmd.
Installation
Add this line to your application's Gemfile:
gem "tty-prompt"
And then execute:
$ bundle
Or install it yourself as:
$ gem install tty-prompt
Contents
- 1. Usage
- 2. Interface
- 3. settings
1. Usage
In order to start asking questions on the command line, create prompt:
require "tty-prompt"
prompt = TTY::Prompt.new
And then call ask
with the question for simple input:
prompt.ask("What is your name?", default: ENV["USER"])
# => What is your name? (piotr)
To confirm input use yes?
:
prompt.yes?("Do you like Ruby?")
# => Do you like Ruby? (Y/n)
If you want to input password or secret information use mask
:
prompt.mask("What is your secret?")
# => What is your secret? ••••
Asking question with list of options couldn't be easier using select
like so:
prompt.select("Choose your destiny?", %w(Scorpion Kano Jax))
# =>
# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)
# ‣ Scorpion
# Kano
# Jax
Also, asking multiple choice questions is a breeze with multi_select
:
choices = %w(vodka beer wine whisky bourbon)
prompt.multi_select("Select drinks?", choices)
# =>
#
# Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)"
# ‣ ⬡ vodka
# ⬡ beer
# ⬡ wine
# ⬡ whisky
# ⬡ bourbon
To ask for a selection from enumerated list you can use enum_select
:
choices = %w(emacs nano vim)
prompt.enum_select("Select an editor?", choices)
# =>
#
# Select an editor?
# 1) emacs
# 2) nano
# 3) vim
# Choose 1-3 [1]:
However, if you have a lot of options to choose from you may want to use expand
:
choices = [
{ key: "y", name: "overwrite this file", value: :yes },
{ key: "n", name: "do not overwrite this file", value: :no },
{ key: "a", name: "overwrite this file and all later files", value: :all },
{ key: "d", name: "show diff", value: :diff },
{ key: "q", name: "quit; do not overwrite this file ", value: :quit }
]
prompt.expand("Overwrite Gemfile?", choices)
# =>
# Overwrite Gemfile? (enter "h" for help) [y,n,a,d,q,h]
If you wish to collect more than one answer use collect
:
result = prompt.collect do
key(:name).ask("Name?")
key(:age).ask("Age?", convert: :int)
key(:address) do
key(:street).ask("Street?", required: true)
key(:city).ask("City?")
key(:zip).ask("Zip?", validate: /\A\d{3}\Z/)
end
end
# =>
# {:name => "Piotr", :age => 30, :address => {:street => "Street", :city => "City", :zip => "123"}}
2. Interface
2.1 ask
In order to ask a basic question do:
prompt.ask("What is your name?")
However, to prompt for more complex input you can use robust API by passing hash of properties or using a block like so:
prompt.ask("What is your name?") do |q|
q.required true
q.validate /\A\w+\Z/
q.modify :capitalize
end
2.1.1 :convert
The convert
property is used to convert input to a required type.
By default no conversion of input is performed. To change this use one of the following conversions:
:boolean
|:bool
- e.g. 'yes/1/y/t/' becomestrue
, 'no/0/n/f' becomesfalse
:date
- parses dates formats "28/03/2020", "March 28th 2020":time
- parses time formats "11:20:03":float
- e.g.-1
becomes-1.0
:int
|:integer
- e.g.+1
becomes1
:sym
|:symbol
- e.g. "foo" becomes:foo
:filepath
- converts to file path:path
|:pathname
- converts toPathname
object:range
- e.g. '1-10' becomes1..10
range object:regexp
- e.g. "foo|bar" becomes/foo|bar/
:uri
- converts toURI
object:list
|:array
- e.g. 'a,b,c' becomes["a", "b", "c"]
:map
|:hash
- e.g. 'a:1 b:2 c:3' becomes{a: "1", b: "2", c: "3"}
In addition you can specify a plural or append list
or array
to any base type:
:ints
or:int_list
- will convert to a list of integers:floats
or:float_list
- will convert to a list of floats:bools
or:bool_list
- will convert to a list of booleans, e.g.t,f,t
becomes[true, false, true]
Similarly, you can append map
or hash
to any base type:
:int_map
|:integer_map
|:int_hash
- will convert to a hash of integers, e.ga:1 b:2 c:3
becomes{a: 1, b: 2, c: 3}
:bool_map
|:boolean_map
|:bool_hash
- will convert to a hash of booleans, e.ga:t b:f c:t
becomes{a: true, b: false, c: true}
By default, map
converts keys to symbols, if you wish to use strings instead specify key type like so:
:str_int_map
- will convert to a hash of string keys and integer values:string_integer_hash
- will convert to a hash of string keys and integer values
For example, if you are interested in range type as answer do the following:
prompt.ask("Provide range of numbers?", convert: :range)
# Provide range of numbers? 1-10
# => 1..10
If, on the other hand, you wish to convert input to a hash of integer values do:
prompt.ask("Provide keys and values:", convert: :int_map)
# Provide keys and values: a=1 b=2 c=3
# => {a: 1, b: 2, c: 3}
If a user provides a wrong type for conversion an error message will be printed in the console:
prompt.ask("Provide digit:", convert: :float)
# Provide digit: x
# >> Cannot convert `x` into 'float' type
You can further customize error message:
prompt.ask("Provide digit:", convert: :float) do |q|
q.convert(:float, "Wrong value of %{value} for %{type} conversion")
# or
q.convert :float
q.messages[:convert?] = "Wrong value of %{value} for %{type} conversion"
end
You can also provide a custom conversion like so:
prompt.ask("Ingredients? (comma sep list)") do |q|
q.convert -> (input) { input.split(/,\s*/) }
end
# Ingredients? (comma sep list) milk, eggs, flour
# => ["milk", "eggs", "flour"]
2.1.2 :default
The :default
option is used if the user presses return key:
prompt.ask("What is your name?", default: "Anonymous")
# =>
# What is your name? (Anonymous)
2.1.3 :value
To pre-populate the input line for editing use :value
option:
prompt.ask("What is your name?", value: "Piotr")
# =>
# What is your name? Piotr
2.1.4 :echo
To control whether the input is shown back in terminal or not use :echo
option like so:
prompt.ask("password:", echo: false)
2.1.5 error messages
By default tty-prompt
comes with predefined error messages for convert
, required
, in
, validate
options.
You can change these and configure to your liking either by passing message as second argument with the option:
prompt.ask("What is your email?") do |q|
q.validate(/\A\w+@\w+\.\w+\Z/, "Invalid email address")
end
Or change the messages
key entry out of :convert?
, :range?
, :required?
and :valid?
:
prompt.ask("What is your email?") do |q|
q.validate(/\A\w+@\w+\.\w+\Z/)
q.messages[:valid?] = "Invalid email address"
end
To change default range validation error message do:
prompt.ask("How spicy on scale (1-5)? ") do |q|
q.in "1-5"
q.messages[:range?] = "%{value} out of expected range %{in}"
end
2.1.6 :in
In order to check that provided input falls inside a range of inputs use the in
option. For example, if we wanted to ask a user for a single digit in given range we may do following:
prompt.ask("Provide number in range: 0-9?") { |q| q.in("0-9") }
2.1.7 :modify
Set the :modify
option if you want to handle whitespace or letter capitalization.
prompt.ask("Enter text:") do |q|
q.modify :strip, :collapse
end
Available letter casing settings are:
:up # change to upper case
:down # change to small case
:capitalize # capitalize each word
Available whitespace settings are:
:trim # remove whitespace from both ends of the input
:strip # same as :trim
:chomp # remove whitespace at the end of input
:collapse # reduce all whitespace to single character
:remove # remove all whitespace
2.1.8 :required
To ensure that input is provided use :required