Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork140
A beautiful and powerful interactive command line prompt
License
piotrmurach/tty-prompt
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A beautiful and powerful interactive command line prompt.
TTY::Prompt provides independent prompt component forTTY toolkit.
- 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
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 likeselect ormulti_select menus may not work on Windows when run from Git Bash. See GitHub suggestedfixes.
For Windows, consider installingConEmu,cmder orPowerCmd.
Add this line to your application's Gemfile:
gem"tty-prompt"
And then execute:
$ bundleOr install it yourself as:
$ gem install tty-prompt- 1. Usage
- 2. Interface
- 3. settings
In order to start asking questions on the command line, create prompt:
require"tty-prompt"prompt=TTY::Prompt.new
And then callask with the question for simple input:
prompt.ask("What is your name?",default:ENV["USER"])# => What is your name? (piotr)
To confirm input useyes?:
prompt.yes?("Do you like Ruby?")# => Do you like Ruby? (Y/n)
If you want to input password or secret information usemask:
prompt.mask("What is your secret?")# => What is your secret? ••••
Asking question with list of options couldn't be easier usingselect like so:
prompt.select("Choose your destiny?",%w(ScorpionKanoJax))# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# Jax
Also, asking multiple choice questions is a breeze withmulti_select:
choices=%w(vodkabeerwinewhiskybourbon)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 useenum_select:
choices=%w(emacsnanovim)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 useexpand:
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 usecollect:
result=prompt.collectdokey(:name).ask("Name?")key(:age).ask("Age?",convert::int)key(:address)dokey(:street).ask("Street?",required:true)key(:city).ask("City?")key(:zip).ask("Zip?",validate:/\A\d{3}\Z/)endend# =># {:name => "Piotr", :age => 30, :address => {:street => "Street", :city => "City", :zip => "123"}}
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.requiredtrueq.validate/\A\w+\Z/q.modify:capitalizeend
Theconvert 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.-1becomes-1.0:int|:integer- e.g.+1becomes1:sym|:symbol- e.g. "foo" becomes:foo:filepath- converts to file path:path|:pathname- converts toPathnameobject:range- e.g. '1-10' becomes1..10range object:regexp- e.g. "foo|bar" becomes/foo|bar/:uri- converts toURIobject: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 appendlist orarray to any base type:
:intsor:int_list- will convert to a list of integers:floatsor:float_list- will convert to a list of floats:boolsor:bool_list- will convert to a list of booleans, e.g.t,f,tbecomes[true, false, true]
Similarly, you can appendmap orhash to any base type:
:int_map|:integer_map|:int_hash- will convert to a hash of integers, e.ga:1 b:2 c:3becomes{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:tbecomes{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")# orq.convert:floatq.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"]
The:default option is used if the user presses return key:
prompt.ask("What is your name?",default:"Anonymous")# =># What is your name? (Anonymous)
To pre-populate the input line for editing use:value option:
prompt.ask("What is your name?",value:"Piotr")# =># What is your name? Piotr
To control whether the input is shown back in terminal or not use:echo option like so:
prompt.ask("password:",echo:false)
By defaulttty-prompt comes with predefined error messages forconvert,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 themessages 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
In order to check that provided input falls inside a range of inputs use thein 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")}
Set the:modify option if you want to handle whitespace or letter capitalization.
prompt.ask("Enter text:")do |q|q.modify:strip,:collapseend
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
To ensure that input is provided use:required option:
prompt.ask("What's your phone number?",required:true)# What's your phone number?# >> Value must be provided
In order to validate that input matches a given pattern you can pass thevalidate option/method.
Validate acceptsRegex,Proc orSymbol.
prompt.ask("What is your username?")do |q|q.validate(/\A[^.]+\.[^.]+\Z/)end
The above can also be expressed as aProc:
prompt.ask("What is your username?")do |q|q.validate->(input){input =~/\A[^.]+\.[^.]+\Z/}end
There is a built-in validation for:email and you can use it directly like so:
prompt.ask("What is your email?"){ |q|q.validate:email}
The default validation message is"Your answer is invalid (must match %{valid})" and you can customise it by passing in a second argument:
prompt.ask("What is your username?")do |q|q.validate(/\A[^.]+\.[^.]+\Z/,"Invalid username: %{value}, must match %{valid}")end
The default message can also be set usingmessages and the:valid? key:
prompt.ask("What is your username?")do |q|q.validate(/\A[^.]+\.[^.]+\Z/)q.messages[:valid?]="Invalid username: %{value}, must match %{valid}")end
In order to ask question that awaits a single character answer usekeypress prompt like so:
prompt.keypress("Press key ?")# Press key?# => a
By default any key is accepted but you can limit keys by using:keys option. Any key event names such as:space or:ctrl_k are valid:
prompt.keypress("Press space or enter to continue",keys:[:space,:return])
Timeout can be set using:timeout option to expire prompt and allow the script to continue automatically:
prompt.keypress("Press any key to continue, resumes automatically in 3 seconds ...",timeout:3)
In addition thekeypress recognises:countdown token when inserted inside the question. It will automatically countdown the time in seconds:
prompt.keypress("Press any key to continue, resumes automatically in :countdown ...",timeout:3)
Asking for multiline input can be done withmultiline method. The reading of input will terminate whenCtrl+d orCtrl+z is pressed. Empty lines will not be included in the returned array.
prompt.multiline("Description?")# Description? (Press CTRL-D or CTRL-Z to finish)# I know not all that may be coming,# but be it what it will,# I'll go to it laughing.# =># ["I know not all that may be coming,\n", "but be it what it will,\n", "I'll go to it laughing.\n"]
Themultiline uses similar options to those supported byask prompt. For example, to provide default description:
prompt.multiline("Description?",default:"A super sweet prompt.")
Or using DSL:
prompt.multiline("Description?")do |q|q.default"A super sweet prompt."q.help"Press thy ctrl+d to end"end
If you require input of confidential information usemask method. By default each character that is printed is replaced by• symbol. All configuration options applicable toask method can be used withmask as well.
prompt.mask("What is your secret?")# => What is your secret? ••••
The masking character can be changed by passing the:mask key:
heart=prompt.decorate(prompt.symbols[:heart] +" ",:magenta)prompt.mask("What is your secret?",mask:heart)# => What is your secret? ❤ ❤ ❤ ❤ ❤
If you don't wish to show any output use:echo option like so:
prompt.mask("What is your secret?",echo:false)
You can also provide validation for your mask to enforce for instance strong passwords:
prompt.mask("What is your secret?",mask:heart)do |q|q.validate(/[a-z\]{5,15}/)end
In order to display a query asking for boolean input from user useyes? like so:
prompt.yes?("Do you like Ruby?")# =># Do you like Ruby? (Y/n)
You can further customize question by passingsuffix,positive,negative andconvert options. Thesuffix changes text of available options, thepositive specifies display string for successful answer andnegative changes display string for negative answer. The final value is a boolean provided theconvert option evaluates to boolean.
It's enough to provide thesuffix option for the prompt to accept matching answers with correct labels:
prompt.yes?("Are you a human?")do |q|q.suffix"Yup/nope"end# =># Are you a human? (Yup/nope)
Alternatively, instead ofsuffix option provide thepositive andnegative labels:
prompt.yes?("Are you a human?")do |q|q.defaultfalseq.positive"Yup"q.negative"Nope"end# =># Are you a human? (yup/Nope)
Finally, providing all available options you can ask fully customized question:
prompt.yes?("Are you a human?")do |q|q.suffix"Agree/Disagree"q.positive"Agree"q.negative"Disagree"q.convert->(input){ !input.match(/^agree$/i).nil?}end# =># Are you a human? (Agree/Disagree)
There is also the opposite for asking confirmation of negative question:
prompt.no?("Do you hate Ruby?")# =># Do you hate Ruby? (y/N)
Similarly toyes? method, you can supply the same options to customize the question.
There are many ways in which you can add menu choices. The simplest way is to create an array of values:
choices=%w(smallmediumlarge)
By default the choice name is also the value the prompt will return when selected. To provide custom values, you can provide a hash with keys as choice names and their respective values:
choices={small:1,medium:2,large:3}prompt.select("What size?",choices)# =># What size? (Press ↑/↓ arrow to move and Enter to select)# ‣ small# medium# large
Finally, you can define an array of choices where each choice is a hash value with:name &:value keys which can include other options for customising individual choices:
choices=[{name:"small",value:1},{name:"medium",value:2,disabled:"(out of stock)"},{name:"large",value:3}]
You can specify:key as an additional option which will be used as short name for selecting the choice via keyboard key press.
Another way to create menu with choices is using the DSL and thechoice method. For example, the previous array of choices with hash values can be translated as:
prompt.select("What size?")do |menu|menu.choicename:"small",value:1menu.choicename:"medium",value:2,disabled:"(out of stock)"menu.choicename:"large",value:3end# =># What size? (Press ↑/↓ arrow to move and Enter to select)# ‣ small# ✘ medium (out of stock)# large
or in a more compact way:
prompt.select("What size?")do |menu|menu.choice"small",1menu.choice"medium",2,disabled:"(out of stock)"menu.choice"large",3end
The:disabled key indicates to display a choice as currently unavailable to select. Disabled choices are displayed with a cross✘ character next to them. If the choice is disabled, it cannot be selected. The value for the:disabled is used next to the choice to provide reason for excluding it from the selection menu. For example:
choices=[{name:"small",value:1},{name:"medium",value:2,disabled:"(out of stock)"},{name:"large",value:3}]
For asking questions involving list of options useselect method by passing the question and possible choices:
prompt.select("Choose your destiny?",%w(ScorpionKanoJax))# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# Jax
You can also provide options through DSL using thechoice method for single entry and/orchoices for more than one choice:
prompt.select("Choose your destiny?")do |menu|menu.choice"Scorpion"menu.choice"Kano"menu.choice"Jax"end# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# Jax
By default the choice name is used as return value, but you can provide your custom values including aProc object:
prompt.select("Choose your destiny?")do |menu|menu.choice"Scorpion",1menu.choice"Kano",2menu.choice"Jax",->{"Nice choice captain!"}end# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# Jax
If you wish you can also provide a simple hash to denote choice name and its value like so:
choices={"Scorpion"=>1,"Kano"=>2,"Jax"=>3}prompt.select("Choose your destiny?",choices)
To mark particular answer as selected usedefault with either an index of the choice starting from1 or a choice's name:
prompt.select("Choose your destiny?")do |menu|menu.default3# or menu.default "Jax"menu.choice"Scorpion",1menu.choice"Kano",2menu.choice"Jax",3end# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# Scorpion# Kano# ‣ Jax
You can navigate the choices using the arrow keys or define your own key mappings (seekeyboard events. When reaching the top/bottom of the list, the selection does not cycle around by default. If you wish to enable cycling, you can passcycle: true toselect andmulti_select:
prompt.select("Choose your destiny?",%w(ScorpionKanoJax),cycle:true)# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# Jax
For ordered choices setenum to any delimiter String. In that way, you can use arrows keys and numbers (0-9) to select the item.
prompt.select("Choose your destiny?")do |menu|menu.enum"."menu.choice"Scorpion",1menu.choice"Kano",2menu.choice"Jax",3end# =># Choose your destiny? (Use ↑/↓ arrow or number (0-9) keys, press Enter to select)# 1. Scorpion# 2. Kano# ‣ 3. Jax
You can configure help message with:help and when to display it with:show_help options. The help can be displayed onstart,never oralways:
choices=%w(ScorpionKanoJax)prompt.select("Choose your destiny?",choices,help:"(Bash keyboard keys)",show_help::always)# =># Choose your destiny? (Bash keyboard keys)# > Scorpion# Kano# Jax
You can configure active marker like so:
choices=%w(ScorpionKanoJax)prompt.select("Choose your destiny?",choices,symbols:{marker:">"})# =># Choose your destiny? (Use ↑/↓ and ←/→ arrow keys, press Enter to select)# > Scorpion# Kano# Jax
By default the menu is paginated if selection grows beyond6 items. To change this setting use:per_page configuration.
letters=("A".."Z").to_aprompt.select("Choose your letter?",letters,per_page:4)# =># Which letter? (Use ↑/↓ and ←/→ arrow keys, press Enter to select)# ‣ A# B# C# D
You can also customise page navigation text using:help option:
letters=("A".."Z").to_aprompt.select("Choose your letter?")do |menu|menu.per_page4menu.help"(Wiggle thy finger up/down and left/right to see more)"menu.choiceslettersend# =># Which letter? (Wiggle thy finger up/down and left/right to see more)# ‣ A# B# C# D
To disable menu choice, use the:disabled key with a value that explains the reason for the choice being unavailable. For example, out of all warriors, the Goro is currently injured:
warriors=["Scorpion","Kano",{name:"Goro",disabled:"(injury)"},"Jax","Kitana","Raiden"]
The disabled choice will be displayed with a cross✘ character next to it and followed by an explanation:
prompt.select("Choose your destiny?",warriors)# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)# ‣ Scorpion# Kano# ✘ Goro (injury)# Jax# Kitana# Raiden
To activate dynamic list searching on letter/number key presses use:filter option:
warriors=%w(ScorpionKanoJaxKitanaRaiden)prompt.select("Choose your destiny?",warriors,filter:true)# =># Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select, and letter keys to filter)# ‣ Scorpion# Kano# Jax# Kitana# Raiden
After the user presses "k":
# =># Choose your destiny? (Filter: "k")# ‣ Kano# Kitana
After the user presses "ka":
# =># Choose your destiny? (Filter: "ka")# ‣ Kano
Filter characters can be deleted partially or entirely via, respectively, Backspace and Canc.
If the user changes or deletes a filter, the choices previously selected remain selected.
For asking questions involving multiple selection list usemulti_select method by passing the question and possible choices:
choices=%w(vodkabeerwinewhiskybourbon)prompt.multi_select("Select drinks?",choices)# =>## Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)"# ‣ ⬡ vodka# ⬡ beer# ⬡ wine# ⬡ whisky# ⬡ bourbon
As a return value, themulti_select will always return an array by default populated with the names of the choices. If you wish to return custom values for the available choices do:
choices={vodka:1,beer:2,wine:3,whisky:4,bourbon:5}prompt.multi_select("Select drinks?",choices)# Provided that vodka and beer have been selected, the function will return# => [1, 2]
Similar toselect method, you can also provide options through DSL using thechoice method for single entry and/orchoices call for more than one choice:
prompt.multi_select("Select drinks?")do |menu|menu.choice:vodka,{score:1}menu.choice:beer,2menu.choice:wine,3menu.choiceswhisky:4,bourbon:5end
To mark choice(s) as selected use thedefault option with either index(s) of the choice(s) starting from1 or choice name(s):
prompt.multi_select("Select drinks?")do |menu|menu.default2,5# or menu.default :beer, :whiskymenu.choice:vodka,{score:10}menu.choice:beer,{score:20}menu.choice:wine,{score:30}menu.choice:whisky,{score:40}menu.choice:bourbon,{score:50}end# =># Select drinks? beer, bourbon# ⬡ vodka# ⬢ beer# ⬡ wine# ⬡ whisky# ‣ ⬢ bourbon
Also like,select, the method takes an optioncycle (which defaults tofalse), which lets you configure whether the selection should cycle around when reaching the top/bottom of the list when navigating:
prompt.multi_select("Select drinks?",%w(vodkabeerwine),cycle:true)
Likeselect, for ordered choices setenum to any delimiter String. In that way, you can use arrows keys and numbers (0-9) to select the item.
prompt.multi_select("Select drinks?")do |menu|menu.enum")"menu.choice:vodka,{score:10}menu.choice:beer,{score:20}menu.choice:wine,{score:30}menu.choice:whisky,{score:40}menu.choice:bourbon,{score:50}end# =># Select drinks? beer, bourbon# ⬡ 1) vodka# ⬢ 2) beer# ⬡ 3) wine# ⬡ 4) whisky# ‣ ⬢ 5) bourbon
And when you press enter you will see the following selected:
# Select drinks? beer, bourbon# => [{score: 20}, {score: 50}]
You can configure help message with:help and when to display it with:show_help options. The help can be displayed onstart,never oralways:
choices={vodka:1,beer:2,wine:3,whisky:4,bourbon:5}prompt.multi_select("Select drinks?",choices,help:"Press beer can against keyboard",show_help::always)# =># Select drinks? (Press beer can against keyboard)"# ‣ ⬡ vodka# ⬡ beer# ⬡ wine# ⬡ whisky# ⬡ bourbon
By default the menu is paginated if selection grows beyond6 items. To change this setting use:per_page configuration.
letters=("A".."Z").to_aprompt.multi_select("Choose your letter?",letters,per_page:4)# =># Which letter? (Use ↑/↓ and ←/→ arrow keys, press Space to select and Enter to finish)# ‣ ⬡ A# ⬡ B# ⬡ C# ⬡ D
To disable menu choice, use the:disabled key with a value that explains the reason for the choice being unavailable. For example, out of all drinks, the sake and beer are currently out of stock:
drinks=["bourbon",{name:"sake",disabled:"(out of stock)"},"vodka",{name:"beer",disabled:"(out of stock)"},"wine","whisky"]
The disabled choice will be displayed with a cross✘ character next to it and followed by an explanation:
prompt.multi_select("Choose your favourite drink?",drinks)# =># Choose your favourite drink? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)# ‣ ⬡ bourbon# ✘ sake (out of stock)# ⬡ vodka# ✘ beer (out of stock)# ⬡ wine# ⬡ whisky
To control whether the selected items are shown on the questionheader use the :echo option:
choices=%w(vodkabeerwinewhiskybourbon)prompt.multi_select("Select drinks?",choices,echo:false)# =># Select drinks?# ⬡ vodka# ⬢ 2) beer# ⬡ 3) wine# ⬡ 4) whisky# ‣ ⬢ 5) bourbon
To activate dynamic list filtering on letter/number typing, use the :filter option:
choices=%w(vodkabeerwinewhiskybourbon)prompt.multi_select("Select drinks?",choices,filter:true)# =># Select drinks? (Use ↑/↓ arrow keys, press Space to select and Enter to finish, and letter keys to filter)# ‣ ⬡ vodka# ⬡ beer# ⬡ wine# ⬡ whisky# ⬡ bourbon
After the user presses "w":
# Select drinks? (Filter: "w")# ‣ ⬡ wine# ⬡ whisky
Filter characters can be deleted partially or entirely via, respectively, Backspace and Canc.
If the user changes or deletes a filter, the choices previously selected remain selected.
Thefilter option is not compatible withenum.
To force the minimum number of choices an user must select, use the:min option:
choices=%w(vodkabeerwinewhiskybourbon)prompt.multi_select("Select drinks?",choices,min:3)# =># Select drinks? (min. 3) vodka, beer# ⬢ vodka# ⬢ beer# ⬡ wine# ⬡ wiskey# ‣ ⬡ bourbon
To limit the number of choices an user can select, use the:max option:
choices=%w(vodkabeerwinewhiskybourbon)prompt.multi_select("Select drinks?",choices,max:3)# =># Select drinks? (max. 3) vodka, beer, whisky# ⬢ vodka# ⬢ beer# ⬡ wine# ⬢ whisky# ‣ ⬡ bourbon
In order to ask for standard selection from indexed list you can useenum_select and pass question together with possible choices:
choices=%w(emacsnanovim)prompt.enum_select("Select an editor?",choices)# =>## Select an editor?# 1) nano# 2) vim# 3) emacs# Choose 1-3 [1]:
Similar toselect andmulti_select, you can provide question options through DSL usingchoice method and/orchoices like so:
choices=%w(nanovimemacs)prompt.enum_select("Select an editor?")do |menu|menu.choice:nano,"/bin/nano"menu.choice:vim,"/usr/bin/vim"menu.choice:emacs,"/usr/bin/emacs"end# =>## Select an editor?# 1) nano# 2) vim# 3) emacs# Choose 1-3 [1]:## Select an editor? /bin/nano
You can change the indexed numbers formatting by passingenum option. Thedefault option lets you specify which choice to mark as selected by default. It accepts an index of the choice starting from1 or a choice name:
choices=%w(nanovimemacs)prompt.enum_select("Select an editor?")do |menu|menu.default2# or menu.defualt "/usr/bin/vim"menu.enum"."menu.choice:nano,"/bin/nano"menu.choice:vim,"/usr/bin/vim"menu.choice:emacs,"/usr/bin/emacs"end# =>## Select an editor?# 1. nano# 2. vim# 3. emacs# Choose 1-3 [2]:## Select an editor? /usr/bin/vim
By default the menu is paginated if selection grows beyond6 items. To change this setting use:per_page configuration.
letters=("A".."Z").to_aprompt.enum_select("Choose your letter?",letters,per_page:4)# =># Which letter?# 1) A# 2) B# 3) C# 4) D# Choose 1-26 [1]:# (Press tab/right or left to reveal more choices)
To make a choice unavailable use the:disabled option and, if you wish, as value provide a reason:
choices=[{name:"Emacs",disabled:"(not installed)"},"Atom","GNU nano",{name:"Notepad++",disabled:"(not installed)"},"Sublime","Vim"]
The disabled choice will be displayed with a cross ✘ character next to it and followed by an explanation:
prompt.enum_select("Select an editor",choices)# =># Select an editor# ✘ 1) Emacs (not installed)# 2) Atom# 3) GNU nano# ✘ 4) Notepad++ (not installed)# 5) Sublime# 6) Vim# Choose 1-6 [2]:
Theexpand provides a compact way to ask a question with many options.
As first argumentexpand takes the message to display and as a second an array of choices. Compared to theselect,multi_select andenum_select, the choices need to be objects that include:key,:name and:value keys. The:key must be a single character. The help choice is added automatically as the last option under the keyh.
choices=[{key:"y",name:"overwrite this file",value::yes},{key:"n",name:"do not overwrite this file",value::no},{key:"q",name:"quit; do not overwrite this file ",value::quit}]
The choices can also be provided through DSL using thechoice method. The:value can be a primitive value orProc instance that gets executed and whose value is used as returned type. For example:
prompt.expand("Overwrite Gemfile?")do |q|q.choicekey:"y",name:"Overwrite"do:okendq.choicekey:"n",name:"Skip",value::noq.choicekey:"a",name:"Overwrite all",value::allq.choicekey:"d",name:"Show diff",value::diffq.choicekey:"q",name:"Quit",value::quitend
The first element in the array of choices or provided viachoice DSL will be the default choice, you can change that by passingdefault option.
prompt.expand("Overwrite Gemfile?",choices)# =># Overwrite Gemfile? (enter "h" for help) [y,n,q,h]
Each time user types an option a hint will be displayed:
# Overwrite Gemfile? (enter "h" for help) [y,n,a,d,q,h] y# >> overwrite this file
If user typesh and presses enter, an expanded view will be shown which further allows to refine the choice:
# Overwrite Gemfile?# y - overwrite this file# n - do not overwrite this file# q - quit; do not overwrite this file# h - print help# Choice [y]:
Runexamples/expand.rb to see the prompt in action.
To show hint by default use:auto_hint option:
prompt.expand("Overwrite Gemfile?",choices,auto_hint:true)# =># Overwrite Gemfile? (enter "h" for help) [y,n,q,h]# >> overwrite this file
In order to collect more than one answer usecollect method. Using thekey you can describe the answers key name. All the methods for asking user input such asask,mask,select can be directly invoked on the key. The key composition is very flexible by allowing nested keys. If you want the value to be automatically converted to required type useconvert.
For example to gather some contact information do:
prompt.collectdokey(:name).ask("Name?")key(:age).ask("Age?",convert::int)key(:address)dokey(:street).ask("Street?",required:true)key(:city).ask("City?")key(:zip).ask("Zip?",validate:/\A\d{3}\Z/)endend# =># {:name => "Piotr", :age => 30, :address => {:street => "Street", :city => "City", :zip => "123"}}
In order to collectmutliple values for a given key in a loop, chainvalues onto thekey desired:
result=prompt.collectdokey(:name).ask("Name?")key(:age).ask("Age?",convert::int)whileprompt.yes?("continue?")key(:addresses).valuesdokey(:street).ask("Street?",required:true)key(:city).ask("City?")key(:zip).ask("Zip?",validate:/\A\d{3}\Z/)endendend# =># {# :name => "Piotr",# :age => 30,# :addresses => [# {:street => "Street", :city => "City", :zip => "123"},# {:street => "Street", :city => "City", :zip => "234"}# ]# }
To suggest possible matches for the user input usesuggest method like so:
prompt.suggest("sta",["stage","stash","commit","branch"])# =># Did you mean one of these?# stage# stash
To customize query text presented pass:single_text and:plural_text options to respectively change the message when one match is found or many.
possible=%w(statusstagestashcommitbranchblame)prompt.suggest("b",possible,indent:4,single_text:"Perhaps you meant?")# =># Perhaps you meant?# blame
If you'd rather not display all possible values in a vertical list, you may consider usingslider. The slider provides easy visual way of picking a value marked by● symbol.
For integers, you can set:min(defaults to 0),:max and:step(defaults to 1) options to configure slider range:
prompt.slider("Volume",min:0,max:100,step:5)# =># Volume ──────────●────────── 50# (Use ←/→ arrow keys, press Enter to select)
For everything else, you can provide an array of your desired choices:
prompt.slider("Letter",('a'..'z').to_a)# =># Letter ────────────●───────────── m# (Use ←/→ arrow keys, press Enter to select)
By default the slider is configured to pick middle of the range as a start value, you can change this by using the:default option:
prompt.slider("Volume",max:100,step:5,default:75)# =># Volume ───────────────●────── 75# (Use ←/→ arrow keys, press Enter to select)
You can also select the default value by name:
prompt.slider("Letter",('a'..'z').to_a,default:'q')# =># Letter ──────────────────●─────── q# (Use ←/→ arrow keys, press Enter to select)
You can also change the default slider formatting using the:format. The value must contain the:slider token to show current value and anysprintf compatible flag for number display, in our case%d:
prompt.slider("Volume",max:100,step:5,default:75,format:"|:slider| %d%%")# =># Volume |───────────────●──────| 75%# (Use ←/→ arrow keys, press Enter to select)
You can also specify slider range with decimal numbers. For example, to have a step of0.5 and display each value with a single decimal place use%f as format:
prompt.slider("Volume",max:10,step:0.5,default:5,format:"|:slider| %.1f")# =># Volume |───────────────●──────| 7.5# (Use ←/→ arrow keys, press Enter to select)
You can alternatively provide a proc/lambda to customize your formatting even further:
slider_format=->(slider,value){"|#{slider}|#{value.zero? ?"muted" :"%.1f"}" %value}prompt.slider("Volume",max:10,step:0.5,default:0,format:slider_format)# =># Volume |●─────────────────────| muted# (Use ←/→ arrow keys, press Enter to select)
If you wish to change the slider handle and the slider range display use:symbols option:
prompt.slider("Volume",max:100,step:5,default:75,symbols:{bullet:"x",line:"_"})# =># Volume _______________x______ 75%# (Use ←/→ arrow keys, press Enter to select)
You can configure help message with:help and when to display with:show_help options. The help can be displayed onstart,never oralways:
prompt.slider("Volume",max:10,default:7,help:"(Move arrows left and right to set value)",show_help::always)# =># Volume ───────────────●────── 7# (Move arrows left and right to set value)
Slider can be configured through DSL as well:
prompt.slider("What size?")do |range|range.max100range.step5range.default75range.format"|:slider| %d%%"end# =># Volume |───────────────●──────| 75%# (Use ←/→ arrow keys, press Enter to select)
prompt.slider("What letter?")do |range|range.choices('a'..'z').to_arange.format"|:slider| %s"range.default'q'end# =># What letter? |──────────────────●───────| q# (Use ←/→ arrow keys, press Enter to select)
To simply print message out to standard output usesay like so:
prompt.say(...)
Thesay method also accepts option:color which supports all the colors provided bypastel
TTY::Prompt provides more specific versions ofsay method to better express intention behind the message such asok,warn anderror.
Print message(s) in green do:
prompt.ok(...)
Print message(s) in yellow do:
prompt.warn(...)
Print message(s) in red do:
prompt.error(...)
All the prompt types, when a key is pressed, fire key press events. You can subscribe to listen to this events by callingon with type of event name.
prompt.on(:keypress){ |event| ...}
The event object is yielded to a block whenever particular event fires. The event haskey andvalue methods. Further, thekey responds to following messages:
name- the name of the event such as :up, :down, letter or digitmeta- true if event is non-standard key associatedshift- true if shift has been pressed with the keyctrl- true if ctrl has been pressed with the key
For example, to add vim like key navigation toselect prompt one would do the following:
prompt.on(:keypress)do |event|ifevent.value =="j"prompt.trigger(:keydown)endifevent.value =="k"prompt.trigger(:keyup)endend
You can subscribe to more than one event:
prompt.on(:keypress){ |key| ...}.on(:keydown){ |key| ...}
The available events are:
:keypress:keydown:keyup:keyleft:keyright:keynum:keytab:keyenter:keyreturn:keyspace:keyescape:keydelete:keybackspace
Many prompts use symbols to display information. You can overwrite the default symbols for all the prompts using the:symbols key and hash of symbol names as value:
prompt=TTY::Prompt.new(symbols:{marker:">"})
The following symbols can be overwritten:
| Symbols | Unicode | ASCII |
|---|---|---|
| tick | ✓ | √ |
| cross | ✘ | x |
| marker | ‣ | > |
| dot | • | . |
| bullet | ● | O |
| line | ─ | - |
| radio_on | ⬢ | (*) |
| radio_off | ⬡ | ( ) |
| arrow_up | ↑ | ↑ |
| arrow_down | ↓ | ↓ |
| arrow_left | ← | ← |
| arrow_right | → | → |
All prompt types support:active_color option. By default it's set to:green value.
Theselect,multi_select,enum_select andexpand prompts use the active color to highlight the currently selected choice.
The answer provided by the user is also highlighted with the active color.
This:active_color as value accepts either a color symbol or callable object.
For example, to change all prompts active color to:cyan do:
prompt=TTY::Prompt.new(active_color::cyan)
You could also usepastel:
notice=Pastel.new.cyan.on_blue.detachprompt=TTY::Prompt.new(active_color:notice)
Or use coloring of your own choice:
prompt = TTY::Prompt.new(active_color: ->(str) { my-color-gem(str) })This option can be applied either globally for all prompts or individually:
prompt.select("What size?",%w(LargeMediumSmall),active_color::cyan)
Pleasesee pastel for all supported colors.
If you wish to disable coloring for a prompt simply pass:enable_color option
prompt=TTY::Prompt.new(enable_color:false)
The:help_color option is used to customize the display color for all the help text. By default it's set to:bright_black value.
Prompts such asselect,multi_select,expand support:help_color. This option can be applied either globally for all prompts or individually.
The:help_color option as value accepts either a color symbol or callable object.
For example, to change all prompts help color to:cyan do:
prompt=TTY::Prompt.new(help_color::cyan)
You could also usepastel:
notice=Pastel.new.cyan.on_blue.detachprompt=TTY::Prompt.new(help_color:notice)
Or use coloring of your own choice:
prompt=TTY::Prompt.new(help_color:->(str){my-color-gem(str)})
Or configure:help_color for an individual prompt:
prompt.select("What size?",%w(LargeMediumSmall),help_color::cyan)
Pleasesee pastel for all supported colors.
By defaultInputInterrupt error will be raised when the user hits the interrupt key(Control-C). However, you can customise this behaviour by passing the:interrupt option. The available options are:
:signal- sends interrupt signal:exit- exits with status code:noop- skips handler- custom proc
For example, to send interrupt signal do:
prompt=TTY::Prompt.new(interrupt::signal)
You can prefix each question asked using the:prefix option. This option can be applied either globally for all prompts or individual for each one:
prompt=TTY::Prompt.new(prefix:"[?] ")
Prompts such asselect,multi_select,expand,slider support:quiet which is used to disable re-echoing of the question and answer after selection is done. This option can be applied either globally for all prompts or individually.
# globalprompt=TTY::Prompt.new(quiet:true)# single promptprompt.select("What is your favorite color?",%w(blueyelloworange))
The prompts that accept line input such asmultiline orask provide history buffer that tracks all the lines entered duringTTY::Prompt.new interactions. The history buffer provides previous or next lines when user presses up/down arrows respectively. However, if you wish to disable this behaviour use:track_history option like so:
prompt=TTY::Prompt.new(track_history:false)
- Fork it (https://github.com/piotrmurach/tty-prompt/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to theContributor Covenant code of conduct.
Copyright (c) 2015 Piotr Murach. See LICENSE for further details.
About
A beautiful and powerful interactive command line prompt
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
