pax_global_header 0000666 0000000 0000000 00000000064 13631250432 0014511 g ustar 00root root 0000000 0000000 52 comment=7fec5d178971274577c61e59f92b9b3cafc5d5b1
tty-prompt-0.21.0/ 0000775 0000000 0000000 00000000000 13631250432 0013730 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/.github/ 0000775 0000000 0000000 00000000000 13631250432 0015270 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000024 13631250432 0017101 0 ustar 00root root 0000000 0000000 github: piotrmurach
tty-prompt-0.21.0/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000001076 13631250432 0020001 0 ustar 00root root 0000000 0000000 ### Are you in the right place?
* For issues or feature requests file a GitHub issue in this repository
* For general questions or discussion post in [Gitter](https://gitter.im/piotrmurach/tty)
### Describe the problem
A brief description of the issue/feature.
### Steps to reproduce the problem
```
Your code here to reproduce the issue
```
### Actual behaviour
What happened? This could be a description, log output, error raised etc...
### Expected behaviour
What did you expect to happen?
### Describe your environment
* OS version:
* Ruby version:
* TTY version:
tty-prompt-0.21.0/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000000677 13631250432 0021103 0 ustar 00root root 0000000 0000000 ### Describe the change
What does this Pull Request do?
### Why are we doing this?
Any related context as to why is this is a desirable change.
### Benefits
How will the library improve?
### Drawbacks
Possible drawbacks applying this change.
### Requirements
Put an X between brackets on each line if you have done the item:
[] Tests written & passing locally?
[] Code style checked?
[] Rebased with `master` branch?
[] Documentation updated?
tty-prompt-0.21.0/.gitignore 0000664 0000000 0000000 00000000223 13631250432 0015715 0 ustar 00root root 0000000 0000000 /.bundle/
/.ruby-version
/.ruby-gemset
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.so
*.o
*.a
mkmf.log
tty-prompt-0.21.0/.rspec 0000664 0000000 0000000 00000000051 13631250432 0015041 0 ustar 00root root 0000000 0000000 --color
--require spec_helper
--warnings
tty-prompt-0.21.0/.rubocop.yml 0000664 0000000 0000000 00000001266 13631250432 0016207 0 ustar 00root root 0000000 0000000 Lint/AssignmentInCondition:
Enabled: false
Metrics/AbcSize:
Max: 30
Metrics/BlockLength:
CountComments: true
Max: 25
ExcludedMethods: []
Exclude:
- "spec/**/*"
Metrics/ClassLength:
Max: 1500
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/LineLength:
Max: 80
Metrics/MethodLength:
Max: 20
Naming/BinaryOperatorParameterName:
Enabled: false
Style/AsciiComments:
Enabled: false
Style/LambdaCall:
SupportedStyles:
- call
- braces
Style/StringLiterals:
EnforcedStyle: single_quotes
Style/TrivialAccessors:
Enabled: false
# { ... } for multi-line blocks is okay
Style/BlockDelimiters:
Enabled: false
Style/CommentedKeyword:
Enabled: false
tty-prompt-0.21.0/.travis.yml 0000664 0000000 0000000 00000000655 13631250432 0016047 0 ustar 00root root 0000000 0000000 ---
language: ruby
before_install: "gem install bundler -v '< 2.0'"
bundler_args: --without tools
script: bundle exec rake ci
rvm:
- 2.0.0
- 2.1.10
- 2.2.10
- 2.3.8
- 2.4.9
- 2.5.7
- 2.6.5
- 2.7.0
- ruby-head
- jruby-9.2.6.0
- jruby-head
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: jruby-9.2.6.0
fast_finish: true
branches:
only: master
notifications:
email: false
tty-prompt-0.21.0/CHANGELOG.md 0000664 0000000 0000000 00000034160 13631250432 0015545 0 ustar 00root root 0000000 0000000 # Change log
## [v0.21.0] - 2019-03-08
### Added
* Add :min option to #multi_select prompt by Katelyn Schiesser(@slowbro)
### Changed
* Change gemspec to remove test artifacts
### Fixed
* Fix :help_color option for multi_selct prompt by @robbystk
## [v0.20.0] - 2019-11-24
### Changed
* Change to update tty-reader dependency
* Change gemspec to include metadata
### Fixed
* Fix Choice#from to differentiate between nil and false by Katelyn Schiesser(@slowbro)
* Fix yes? and no? prompts to stop raising on invalid/blank input by Katelyn Schiesser(@slowbro)
* Fix Ruby 2.7 keyword arguments warnings
* Fix question validation to work with nil input
## [v0.19.0] - 2019-05-27
### Added
* Add Prompt#debug to allow displaying values in terminal's top right corner
* Add :max to limit number of choices in #multi_select prompt
* Add :value to pre populate #ask prompt line content
* Add :auto_hint to expand default hint in #expand prompt by Ewoudt Kellerman(@hellola)
* Add Timer to track and timeout code execution
### Changed
* Change Paginator to expose #start_index & #end_index
* Change Paginator to figure out #start_index based on per page size and adjust boundaries to match active selection
* Change #ask prompt to allow no question
* Change #enum_select to automatically assigned non-disabled default option
* Change #enum_select to set default choice when navigating by page
* Change #select & #multi_select to allow navigation by page with left/right keys
* Change #keypress to use Timer
* Change Choice#from to allow any object coercible to string
* Change to remove test artifacts from the gem bundle
* Change to remove timers dependency
* Change to update tty-reader dependency
## [v0.18.1] - 2018-12-29
### Changed
* Change #multi_select & #select to auto select first non-disabled active choice
### Fixed
* Fix #select, #multi_select & #enum_select to allow for symbols as choice names
## [v0.18.0] - 2018-11-24
### Changed
* Change to update tty-reader dependency
* Remove encoding magic comments
### Fixed
* Fix #keypress to stop using the :nonblock option
* Fix input reading to correctly capture the Esc key(#84)
* Fix line editing when cursor is on second to last character(#94)
## [v0.17.2] - 2018-11-01
### Fixed
* Fix #yes? & #no? prompt suffix option to all non-standard characters by Rui(@rpbaltazar)
## [v0.17.1] - 2018-10-03
### Change
* Change #select, #multi_select to allow alphanumeric, punctuation and space characters in filters
### Fixed
* Fix #select by making filter an array to avoid frozen string issues by Chris Hoffman(@yarmiganosca)
## [v0.17.0] - 2018-08-05
### Changed
* Change to update tty-reader & tty-cursor dependencies
* Change to directly require files in gemspec
## [v0.16.1] - 2018-04-29
### Fixed
* Fix key events subscription to only listen for the current prompt events
## [v0.16.0] - 2018-03-11
### Added
* Add :disabled key to Choice
* Add ability to disable choices in #select, #multi_selct & #enum_select prompts
* Add #frozen_string_literal to all files
### Changed
* Change Choice#from to allow parsing different data structures
* Change all classes to prevent strings mutations
* Change Timeout to cleanly terminate keypress input without raising errors
### Fixed
* Fix #select, #enum_select & #multi_select navigation to work correctly with items longer than terminal screen width
* Fix timeout on Ruby 2.5 and stop raising Timeout::Error
## [v0.15.0] - 2018-02-08
### Added
* Add ability to filter list items in #select, #multi_select & #enum_selct prompts by Saverio Miroddi(@saveriomiroddi)
* Add support for array of values for an answer collector key by Danny Hadley(@dadleyy)
### Changed
* Relax dependency on timers by Andy Brody(@brodygov)
## [v0.14.0] - 2018-01-01
### Added
* Add :cycle option to #select, #multi_select & #enum_select prompts to allow toggling between infinite and bounded list by Jonas Müller(@muellerj)
### Changed
* Change #multi_selct, #select & #enum_select to stop cycling options by default by Jona Müller(@muellerj)
* Change gemspec to require ruby >= 2.0.0
* Change #slider prompt to display slider next to query and help underneath
* Change to use tty-reader v0.2.0 with new line editing features for processing long inputs
### Fixed
* Fix Paginator & EnumPaginator to allow only positive integer values by Andy Brody(@ab)
* Fix EnumSelect to report on default option out of range and raise correctly
* Fix #ask :file & :path converters to correctly locate the files
* Fix #ask, #multiline to correctly handle long strings that wrap around screen
* Fix #slider prompt to correctly scale sliding
## [v0.13.2] - 2017-08-30
### Changed
* Change to extract TTY::Prompt::Reader to its own dependency
## [v0.13.1] - 2017-08-16
### Added
* Add ability to manually cancel the time scheduler
### Changed
* Change #keypress to use new scheduler cancelling
* Change Reader to inline interrupt to allow for early exit
### Fix
* Fix keypress reading on Windows to distinguish between blocking & non-blocking IO
## [v0.13.0] - 2017-08-11
### Changed
* Change Timeout to use clock time instead of sleep to measure interval
* Upgrade tty-cursor to fix save & restore
### Fixed
* Fix keypress with timeout option to cleanly stop timeout thread
* Fix Reader on Windows to stop blocking when waiting for key press
## [v0.12.0] - 2017-03-19
### Added
* Add Multiline question type
* Add Keypress question type
* Add Reader::History for storing buffered lines
* Add Reader::Line for line abstraction
### Changed
* Remove :read option from Question
* Chnage Reader#read_line to handle raw mode for processing special
characters such as Ctrl+x, navigate through history buffer
using up/down arrows, allow editing current line by moving left/right
with arrow keys and inserting content
* Change Reader#read_multiline to gather multi line input correctly,
skip empty lines and terminate when Ctrl+d and Ctrl+z are pressed
* Change Reader::Mode to check if tty is available by Matt Martyn (@MMartyn)
* Change #keypress prompt to correctly refresh line and accept :keys & :timeout options
### Fixed
* Fix issue with #select, #multi_selct, #enum_select when choices are
provided as hash object together with prompt options.
* Fix issue with default parameter for yes?/no? prompt by Carlos Fonseca (@carlosefonseca)
* Fix List#help to allow setting help text through DSL
## [v0.11.0] - 2017-02-26
### Added
* Add Console for reading input characters on Unix systems
* Add WinConsole for reading input characters on Windows systems
* Add WindowsApi to allow for calls to external Windows api
* Add echo support to multilist by Keith Keith T. Garner(@ktgeek)
### Changed
* Change Reader to use Console for input reading
* Change Codes to use codepoints instead of strings
* Change Reader#read_line to match #gets behaviour
* Change Symbols to provide Unicode support on windows
* Change Slider to display Unicode when possible
* Change ConverterRegistry to be immutable
* Change Reader to expose #trigger in place of #publish for events firing
### Fixed
* Fix `modify` throwing exception, when user enters empty input by Igor Rzegocki(@ajgon)
* Fix #clear_line behaviour by using tty-cursor 0.4.0 to work in all terminals
* Fix paging issue for lists shorter than :per_page value repeating title
* Fix #mask prompt to correctly match input on Windows
* Fix @mask to use default error messages
* Fix #select & #multi_select prompts to allow changing options with arrow keys on Windows
* Fix #echo to work correctly in zsh shell by štef(@d4be4st)
* Fix Slider#keyright event accepting max value outside of range
* Fix 2.4.0 conversion errors by using necromancer 0.4.0
* Fix #enum_select preventing selection of first item
## [v0.10.1] - 2017-02-06
### Fixed
* Fix File namespacing
## [v0.10.0] - 2017-01-01
### Added
* Add :enable_color option for toggling colors support
### Changed
* Update pastel dependency version
## [v0.9.0] - 2016-12-20
### Added
* Add ability to paginate choices list for #select, #multi_select & #enum_select
with :per_page, :page_info and :default options
* Add ability to switch through options in #select & #multi_select using the tab key
### Fixed
* Fix readers to accept multibyte characters reported by Jaehyun Shin(@keepcosmos)
## [v0.8.0] - 2016-11-29
### Added
* Add ability to publish custom key events for VIM keybindings customisations etc...
### Fixed
* Fix Reader#read_char to use Ruby internal buffers instead of direct system call by @kke(Kimmo Lehto)
* Fix issue with #ask required & validate checks to take into account required when validating values
* Fix bug with #read_keypress to handle function keys and meta navigation keys
* Fix issue with default messages not displaying for `range`, `required` and `validate`
## [v0.7.1] - 2016-08-07
### Fixed
* Fix Reader::Mode to include standard io library
## [v0.7.0] - 2016-07-17
### Added
* Add :interrupt_handler option to customise keyboard interrupt behaviour
### Changed
* Remove tty-platform dependency
### Fixed
* Fix Reader#read_keypress issue when handling interrupt signal by Ondrej Moravcik(@ondra-m)
* Fix raw & echo modes to use standard library support by Kim Burgestrand(@Burgestrand)
## [v0.6.0] - 2016-05-21
### Changed
* Upgrade tty-cursor dependency
### Fixed
* Fix issue with reader trapping signals by @kylekyle
* Fix expand to use new prev_line implementation
## [v0.5.0] - 2016-03-28
### Added
* Add ConfirmQuestion for #yes? & #no? calls
* Add ability to collect more than one answer through #collect call
* Add Choices#find_by for selecting choice based on attribute
* Add Prompt#expand for expanding key options
* Add :active_color, :help_color, :prefix options for customizing prompts display
### Changed
* Change Choice#from to allow for coersion of complex objects with keys
* Change Choices#pluck to search through object attributes
* Change #select :enum option help text to display actual numbers range
### Fixed
* Fix #no? to correctly ask negative question by @ondra-m
* Fix #ask :default option to handle nil or empty string
* Fix #multi_select :default option and color changing
## [v0.4.0] - 2016-02-08
### Added
* Add :enum option for #select & #multi_select to allow for numerical selection by @rtoshiro
* Add new key event types to KeyEvent
* Add #slider for picking values from range of numbers
* Add #enum_select for selecting option from enumerated list
* Add ability to configure error messages for #ask call
* Add new ConversionError type
### Changed
* Move #blank? to Utils
* Update pastel dependency
## [v0.3.0] - 2015-12-28
### Added
* Add prefix option to prompt to customize #ask, #select, #multi_select
* Add default printing to #ask
* Add #yes?/#no? boolean queries
* Add Evaluator and Result for validation checking to Question
* Add ability for #ask to display error messages on failed validation
* Add ability to specify in-built names for validation e.i. :email
* Add KeyEvent for keyboard events publishing to Reader
* Add #read_multiline to Reader
* Add :convert option for ask configuration
* Add ability to specify custom proc converters
* Add #ask_keypress to gather character input
* Add #ask_multiline to gather multiline input
* Add MaskedQuestion & #mask method for masking input stream characters
### Changed
* Change Reader#read_keypress to be robust and read correctly byte sequences
* Change Reader#getc to #read_line and extend arguments with echo option
* Extract cursor movement to dependency tty-cursor
* Change List & MultiList to subscribe to keyboard events
* Change to move mode inside reader namespace
* Remove Response & Error objects
* Remove :char option from #ask
* Change :read option to specify mode of reading out of :line, :multiline, :keypress
* Rename #confirm to #ok
## [v0.2.0] - 2015-11-23
### Added
* Add ability to select choice form list #select
* Add ability to select multiple options #multi_select
* Add :read option to #ask for reading specific type input
### Changed
* Change #ask api to be similar to #select and #multi_select behaviour
* Change #ask :argument option to be :required
* Remove :valid option from #ask as #select is a better solution
## [v0.1.0] - 2015-11-01
* Initial implementation and release
[v0.20.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.19.0...v0.20.0
[v0.19.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.18.1...v0.19.0
[v0.18.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.18.0...v0.18.1
[v0.18.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.17.2...v0.18.0
[v0.17.2]: https://github.com/piotrmurach/tty-prompt/compare/v0.17.1...v0.17.2
[v0.17.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.17.0...v0.17.1
[v0.17.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.16.1...v0.17.0
[v0.16.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.16.0...v0.16.1
[v0.16.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.15.0...v0.16.0
[v0.15.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.14.0...v0.15.0
[v0.14.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.2...v0.14.0
[v0.13.2]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.1...v0.13.2
[v0.13.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.0...v0.13.1
[v0.13.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.12.0...v0.13.0
[v0.12.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.11.0...v0.12.0
[v0.11.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.10.1...v0.11.0
[v0.10.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.10.0...v0.10.1
[v0.10.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.9.0...v0.10.0
[v0.9.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.8.0...v0.9.0
[v0.8.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.7.1...v0.8.0
[v0.7.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.7.0...v0.7.1
[v0.7.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.6.0...v0.7.0
[v0.6.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.5.0...v0.6.0
[v0.5.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.4.0...v0.5.0
[v0.4.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.3.0...v0.4.0
[v0.3.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.2.0...v0.3.0
[v0.2.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.1.0...v0.2.0
[v0.1.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.1.0
tty-prompt-0.21.0/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006240 13631250432 0016531 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at piotr@piotrmurach.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/
tty-prompt-0.21.0/Gemfile 0000664 0000000 0000000 00000000411 13631250432 0015217 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gemspec
#gem 'tty-reader', path: '../tty-reader'
#gem 'tty-reader', git: 'https://github.com/piotrmurach/tty-reader'
group :test do
gem 'benchmark-ips', '~> 2.7.2'
gem 'simplecov', '~> 0.16.1'
gem 'coveralls', '~> 0.8.22'
end
tty-prompt-0.21.0/LICENSE.txt 0000664 0000000 0000000 00000002055 13631250432 0015555 0 ustar 00root root 0000000 0000000 Copyright (c) 2015 Piotr Murach
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
tty-prompt-0.21.0/README.md 0000664 0000000 0000000 00000122631 13631250432 0015214 0 ustar 00root root 0000000 0000000
# TTY::Prompt [][gitter]
[][gem]
[][travis]
[][appveyor]
[][codeclimate]
[][coverage]
[][inchpages]
[gitter]: https://gitter.im/piotrmurach/tty
[gem]: http://badge.fury.io/rb/tty-prompt
[travis]: http://travis-ci.org/piotrmurach/tty-prompt
[appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-prompt
[codeclimate]: https://codeclimate.com/github/piotrmurach/tty-prompt
[coverage]: https://coveralls.io/github/piotrmurach/tty-prompt
[inchpages]: http://inch-ci.org/github/piotrmurach/tty-prompt
> A beautiful and powerful interactive command line prompt.
**TTY::Prompt** provides independent prompt component for [TTY](https://github.com/piotrmurach/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](https://github.com/git-for-windows/git/wiki/FAQ#some-native-console-programs-dont-work-when-run-from-git-bash-how-to-fix-it).
For Windows, consider installing [ConEmu](https://conemu.github.io/), [cmder](http://cmder.net/) or [PowerCmd](http://www.powercmd.com/).
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'tty-prompt'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install tty-prompt
## Contents
* [1. Usage](#1-usage)
* [2. Interface](#2-interface)
* [2.1 ask](#21-ask)
* [2.1.1 convert](#211-convert)
* [2.1.2 default](#212-default)
* [2.1.3 value](#213-value)
* [2.1.4 echo](#214-echo)
* [2.1.5 error messages](#215-error-messages)
* [2.1.6 in](#216-in)
* [2.1.7 modify](#217-modify)
* [2.1.8 required](#218-required)
* [2.1.9 validate](#219-validate)
* [2.2 keypress](#22-keypress)
* [2.2.1 :timeout](#221-timeout)
* [2.3 multiline](#23-multiline)
* [2.4 mask](#24-mask)
* [2.5 yes?/no?](#25-yesno)
* [2.6 menu](#26-menu)
* [2.6.1 choices](#261-choices)
* [2.6.1.1 :disabled](#2611-disabled)
* [2.6.2 select](#262-select)
* [2.6.2.1 :per_page](#2621-per_page)
* [2.6.2.2 :disabled](#2622-disabled)
* [2.6.2.3 :filter](#2623-filter)
* [2.6.3 multi_select](#263-multi_select)
* [2.6.3.1 :disabled](#2631-disabled)
* [2.6.3.2 :echo](#2632-echo)
* [2.6.3.3 :filter](#2633-filter)
* [2.6.3.4 :min](#2634-min)
* [2.6.3.5 :max](#2635-max)
* [2.6.4 enum_select](#264-enum_select)
* [2.6.4.1 :per_page](#2641-per_page)
* [2.6.4.1 :disabled](#2641-disabled)
* [2.7 expand](#27-expand)
* [2.7.1 auto_hint](#271-auto_hint)
* [2.8 collect](#28-collect)
* [2.9 suggest](#29-suggest)
* [2.10 slider](#210-slider)
* [2.11 say](#211-say)
* [2.11.1 ok](#2111-ok)
* [2.11.2 warn](#2112-warn)
* [2.11.3 error](#2113-error)
* [2.12 keyboard events](#212-keyboard-events)
* [3. settings](#3-settings)
* [3.1 :symbols](#31-symbols)
* [3.2 :active_color](#32-active_color)
* [3.3 :enable_color](#33-enable-color)
* [3.4 :help_color](#34-help_color)
* [3.5 :interrupt](#35-interrupt)
* [3.6 :prefix](#36-prefix)
* [3.7 :track_history](#37-track_history)
## 1. Usage
In order to start asking questions on the command line, create prompt:
```ruby
require "tty-prompt"
prompt = TTY::Prompt.new
```
And then call `ask` with the question for simple input:
```ruby
prompt.ask('What is your name?', default: ENV['USER'])
# => What is your name? (piotr)
```
To confirm input use `yes?`:
```ruby
prompt.yes?('Do you like Ruby?')
# => Do you like Ruby? (Y/n)
```
If you want to input password or secret information use `mask`:
```ruby
prompt.mask("What is your secret?")
# => What is your secret? ••••
```
Asking question with list of options couldn't be easier using `select` like so:
```ruby
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`:
```ruby
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`:
```ruby
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`:
```ruby
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`:
```ruby
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:
```ruby
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:
```ruby
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 is performed. The following conversions are provided:
```ruby
:bool # true or false for strings such as "Yes", "No"
:date # date type
:datetime # datetime type
:file # File object
:float # decimal or error if cannot convert
:int # integer or error if cannot convert
:path # Pathname object
:range # range type
:regexp # regex expression
:string # string
:symbol # symbol
```
For example, if you are interested in range type as answer do the following:
```ruby
prompt.ask("Provide range of numbers?", convert: :range)
# Provide range of numbers? 1-10
# => 1..10
```
You can also provide a custom conversion like so:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
prompt.ask('password:', echo: false)
```
#### 2.1.5 error messages
By default `tty-prompt` comes with predefined error messages for `required`, `in`, `validate` options.
You can change these and configure to your liking either by passing message as second argument with the option:
```ruby
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 `:required?`, `:valid?`, `:range?`:
```ruby
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:
```ruby
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:
```ruby
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.
```ruby
prompt.ask('Enter text:') do |q|
q.modify :strip, :collapse
end
```
Available letter casing settings are:
```ruby
:up # change to upper case
:down # change to small case
:capitalize # capitalize each word
```
Available whitespace settings are:
```ruby
: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` option:
```ruby
prompt.ask("What's your phone number?", required: true)
# What's your phone number?
# >> Value must be provided
```
#### 2.1.9 validate
In order to validate that input matches a given pattern you can pass the `validate` option. Validate setting accepts `Regex`, `Proc` or `Symbol`.
```ruby
prompt.ask('What is your username?') do |q|
q.validate /^[^\.]+\.[^\.]+/
end
```
```ruby
prompt.ask('What is your username?') do |q|
q.validate { |input| input =~ /^[^\.]+\.[^\.]+/ }
end
```
The **TTY::Prompt** comes with built-in validations for `:email` and you can use them directly like so:
```ruby
prompt.ask('What is your email?') { |q| q.validate :email }
```
### 2.2. keypress
In order to ask question that awaits a single character answer use `keypress` prompt like so:
```ruby
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:
```ruby
prompt.keypress("Press space or enter to continue", keys: [:space, :return])
```
#### 2.2.1 timeout
Timeout can be set using `:timeout` option to expire prompt and allow the script to continue automatically:
```ruby
prompt.keypress("Press any key to continue, resumes automatically in 3 seconds ...", timeout: 3)
```
In addition the `keypress` recognises `:countdown` token when inserted inside the question. It will automatically countdown the time in seconds:
```ruby
prompt.keypress("Press any key to continue, resumes automatically in :countdown ...", timeout: 3)
```
### 2.3 multiline
Asking for multiline input can be done with `multiline` method. The reading of input will terminate when `Ctrl+d` or `Ctrl+z` is pressed. Empty lines will not be included in the returned array.
```ruby
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"]
```
The `multiline` uses similar options to those supported by `ask` prompt. For example, to provide default description:
```ruby
prompt.multiline("Description?", default: 'A super sweet prompt.')
```
Or using DSL:
```ruby
prompt.multiline("Description?") do |q|
q.default 'A super sweet prompt.'
q.help 'Press thy ctrl+d to end'
end
```
### 2.4 mask
If you require input of confidential information use `mask` method. By default each character that is printed is replaced by `•` symbol. All configuration options applicable to `ask` method can be used with `mask` as well.
```ruby
prompt.mask('What is your secret?')
# => What is your secret? ••••
```
The masking character can be changed by passing `:symbols` option with `:mask` key:
```ruby
heart = prompt.decorate(prompt.symbols[:heart] + ' ', :magenta)
prompt.mask('What is your secret?', symbols: {mask: heart})
# => What is your secret? ❤ ❤ ❤ ❤ ❤
```
If you don't wish to show any output use `:echo` option like so:
```ruby
prompt.mask('What is your secret?', echo: false)
```
You can also provide validation for your mask to enforce for instance strong passwords:
```ruby
prompt.mask('What is your secret?', mask: heart) do |q|
q.validate(/[a-z\ ]{5,15}/)
end
```
### 2.5 yes?/no?
In order to display a query asking for boolean input from user use `yes?` like so:
```ruby
prompt.yes?('Do you like Ruby?')
# =>
# Do you like Ruby? (Y/n)
```
You can further customize question by passing `suffix`, `positive`, `negative` and `convert` options. The `suffix` changes text of available options, the `positive` specifies display string for successful answer and `negative` changes display string for negative answer. The final value is a boolean provided the `convert` option evaluates to boolean.
It's enough to provide the `suffix` option for the prompt to accept matching answers with correct labels:
```ruby
prompt.yes?("Are you a human?") do |q|
q.suffix 'Yup/nope'
end
# =>
# Are you a human? (Yup/nope)
```
Alternatively, instead of `suffix` option provide the `positive` and `negative` labels:
```ruby
prompt.yes?("Are you a human?") do |q|
q.default false
q.positive 'Yup'
q.negative 'Nope'
end
# =>
# Are you a human? (yup/Nope)
```
Finally, providing all available options you can ask fully customized question:
```ruby
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:
```ruby
prompt.no?('Do you hate Ruby?')
# =>
# Do you hate Ruby? (y/N)
```
Similarly to `yes?` method, you can supply the same options to customize the question.
### 2.6 menu
### 2.6.1 choices
There are many ways in which you can add menu choices. The simplest way is to create an array of values:
```ruby
choices = %w(small medium large)
```
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:
```ruby
choices = {small: 1, medium: 2, large: 3}
```
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:
```ruby
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 the `choice` method. For example, the previous array of choices with hash values can be translated as:
```ruby
prompt.select('What size?') do |menu|
menu.choice name: 'small', value: 1
menu.choice name: 'medium', value: 2, disabled: '(out of stock)'
menu.choice name: 'large', value: 3
end
```
or in a more compact way:
```ruby
prompt.select('What size?') do |menu|
menu.choice 'small', 1
menu.choice 'medium', 2, disabled: '(out of stock)'
menu.choice 'large', 3
end
```
#### 2.6.1.1 `:disabled`
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:
```ruby
choices = [
{name: 'small', value: 1},
{name: 'medium', value: 2, disabled: '(out of stock)'}
{name: 'large', value: 3}
]
```
### 2.6.2 select
For asking questions involving list of options use `select` method by passing the question and possible choices:
```ruby
prompt.select("Choose your destiny?", %w(Scorpion Kano Jax))
# =>
# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)
# ‣ Scorpion
# Kano
# Jax
```
You can also provide options through DSL using the `choice` method for single entry and/or `choices` for more than one choice:
```ruby
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 a `Proc` object:
```ruby
prompt.select("Choose your destiny?") do |menu|
menu.choice 'Scorpion', 1
menu.choice 'Kano', 2
menu.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:
```ruby
choices = {'Scorpion' => 1, 'Kano' => 2, 'Jax' => 3}
prompt.select("Choose your destiny?", choices)
```
To mark particular answer as selected use `default` with index of the option starting from `1`:
```ruby
prompt.select("Choose your destiny?") do |menu|
menu.default 3
menu.choice 'Scorpion', 1
menu.choice 'Kano', 2
menu.choice 'Jax', 3
end
# =>
# 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 (see [keyboard events](#212-keyboard-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 pass `cycle: true` to `select` and `multi_select`:
```ruby
prompt.select("Choose your destiny?", %w(Scorpion Kano Jax), cycle: true)
# =>
# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)
# ‣ Scorpion
# Kano
# Jax
```
For ordered choices set `enum` to any delimiter String. In that way, you can use arrows keys and numbers (0-9) to select the item.
```ruby
prompt.select("Choose your destiny?") do |menu|
menu.enum '.'
menu.choice 'Scorpion', 1
menu.choice 'Kano', 2
menu.choice 'Jax', 3
end
# =>
# 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 and/or marker like so
```ruby
choices = %w(Scorpion Kano Jax)
prompt.select("Choose your destiny?", choices, help: "(Bash keyboard)", symbols: {marker: '>'})
# =>
# Choose your destiny? (Bash keyboard)
# > Scorpion
# Kano
# Jax
```
#### 2.6.1.1 `:per_page`
By default the menu is paginated if selection grows beyond `6` items. To change this setting use `:per_page` configuration.
```ruby
letters = ('A'..'Z').to_a
prompt.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:
```ruby
letters = ('A'..'Z').to_a
prompt.select("Choose your letter?") do |menu|
menu.per_page 4
menu.help '(Wiggle thy finger up/down and left/right to see more)'
menu.choices letters
end
# =>
# Which letter? (Wiggle thy finger up/down and left/right to see more)
# ‣ A
# B
# C
# D
```
#### 2.6.2.2 `:disabled`
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:
```ruby
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:
```ruby
prompt.select('Choose your destiny?', warriors)
# =>
# Choose your destiny? (Use ↑/↓ arrow keys, press Enter to select)
# ‣ Scorpion
# Kano
# ✘ Goro (injury)
# Jax
# Kitana
# Raiden
```
#### 2.6.2.3 `:filter`
To activate dynamic list searching on letter/number key presses use `:filter` option:
```ruby
warriors = %w(Scorpion Kano Jax Kitana Raiden)
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":
```ruby
# =>
# Choose your destiny? (Filter: "k")
# ‣ Kano
# Kitana
```
After the user presses "ka":
```ruby
# =>
# 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.
### 2.6.3 multi_select
For asking questions involving multiple selection list use `multi_select` method by passing the question and possible choices:
```ruby
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
```
As a return value, the `multi_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:
```ruby
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 to `select` method, you can also provide options through DSL using the `choice` method for single entry and/or `choices` call for more than one choice:
```ruby
prompt.multi_select("Select drinks?") do |menu|
menu.choice :vodka, {score: 1}
menu.choice :beer, 2
menu.choice :wine, 3
menu.choices whisky: 4, bourbon: 5
end
```
To mark choice(s) as selected use the `default` option with index(s) of the option(s) starting from `1`:
```ruby
prompt.multi_select("Select drinks?") do |menu|
menu.default 2, 5
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
# ⬡ vodka
# ⬢ beer
# ⬡ wine
# ⬡ whisky
# ‣ ⬢ bourbon
```
Like `select`, for ordered choices set `enum` to any delimiter String. In that way, you can use arrows keys and numbers (0-9) to select the item.
```ruby
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:
```ruby
# Select drinks? beer, bourbon
# => [{score: 20}, {score: 50}]
```
Also like, `select`, the method takes an option `cycle` (which defaults to `false`), which lets you configure whether the selection should cycle around when reaching the top/bottom of the list when navigating:
```ruby
prompt.multi_select("Select drinks?", %w(vodka beer wine), cycle: true)
```
You can configure help message and/or marker like so
```ruby
choices = {vodka: 1, beer: 2, wine: 3, whisky: 4, bourbon: 5}
prompt.multi_select("Select drinks?", choices, help: 'Press beer can against keyboard')
# =>
# Select drinks? (Press beer can against keyboard)"
# ‣ ⬡ vodka
# ⬡ beer
# ⬡ wine
# ⬡ whisky
# ⬡ bourbon
```
By default the menu is paginated if selection grows beyond `6` items. To change this setting use `:per_page` configuration.
```ruby
letters = ('A'..'Z').to_a
prompt.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
```
#### 2.6.3.1 `:disabled`
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:
```ruby
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:
```ruby
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
```
#### 2.6.3.2 `:echo`
To control whether the selected items are shown on the question
header use the :echo option:
```ruby
choices = %w(vodka beer wine whisky bourbon)
prompt.multi_select("Select drinks?", choices, echo: false)
# =>
# Select drinks?
# ⬡ vodka
# ⬢ 2) beer
# ⬡ 3) wine
# ⬡ 4) whisky
# ‣ ⬢ 5) bourbon
```
#### 2.6.3.3 `:filter`
To activate dynamic list filtering on letter/number typing, use the :filter option:
```ruby
choices = %w(vodka beer wine whisky bourbon)
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":
```ruby
# 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.
The `filter` option is not compatible with `enum`.
#### 2.6.3.4 `:min`
To force the minimum number of choices an user must select, use the `:min` option:
```ruby
choices = %w(vodka beer wine whisky bourbon)
prompt.multi_select("Select drinks?", choices, min: 3)
# =>
# Select drinks? (min. 3) vodka, beer
# ⬢ vodka
# ⬢ beer
# ⬡ wine
# ⬡ wiskey
# ‣ ⬡ bourbon
```
#### 2.6.3.5 `:max`
To limit the number of choices an user can select, use the `:max` option:
```ruby
choices = %w(vodka beer wine whisky bourbon)
prompt.multi_select("Select drinks?", choices, max: 3)
# =>
# Select drinks? (max. 3) vodka, beer, whisky
# ⬢ vodka
# ⬢ beer
# ⬡ wine
# ⬢ whisky
# ‣ ⬡ bourbon
```
### 2.6.4 enum_select
In order to ask for standard selection from indexed list you can use `enum_select` and pass question together with possible choices:
```ruby
choices = %w(emacs nano vim)
prompt.enum_select("Select an editor?")
# =>
#
# Select an editor?
# 1) nano
# 2) vim
# 3) emacs
# Choose 1-3 [1]:
```
Similar to `select` and `multi_select`, you can provide question options through DSL using `choice` method and/or `choices` like so:
```ruby
choices = %w(nano vim emacs)
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 by passing `enum` option and the default option by using `default` like so
```ruby
choices = %w(nano vim emacs)
prompt.enum_select("Select an editor?") do |menu|
menu.default 2
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
```
#### 2.6.4.1 `:per_page`
By default the menu is paginated if selection grows beyond `6` items. To change this setting use `:per_page` configuration.
```ruby
letters = ('A'..'Z').to_a
prompt.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)
```
#### 2.6.4.2 `:disabled`
To make a choice unavailable use the `:disabled` option and, if you wish, as value provide a reason:
```ruby
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:
```ruby
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]:
```
### 2.7 expand
The `expand` provides a compact way to ask a question with many options.
As first argument `expand` takes the message to display and as a second an array of choices. Compared to the `select`, `multi_select` and `enum_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 key `h`.
```ruby
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 the `choice` method. The `:value` can be a primitive value or `Proc` instance that gets executed and whose value is used as returned type. For example:
```ruby
prompt.expand('Overwrite Gemfile?') do |q|
q.choice key: 'y', name: 'Overwrite' do :ok end
q.choice key: 'n', name: 'Skip', value: :no
q.choice key: 'a', name: 'Overwrite all', value: :all
q.choice key: 'd', name: 'Show diff', value: :diff
q.choice key: 'q', name: 'Quit', value: :quit
end
```
The first element in the array of choices or provided via `choice` DSL will be the default choice, you can change that by passing `default` option.
```ruby
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:
```ruby
# Overwrite Gemfile? (enter "h" for help) [y,n,a,d,q,h] y
# >> overwrite this file
```
If user types `h` and presses enter, an expanded view will be shown which further allows to refine the choice:
```ruby
# Overwrite Gemfile?
# y - overwrite this file
# n - do not overwrite this file
# q - quit; do not overwrite this file
# h - print help
# Choice [y]:
```
Run `examples/expand.rb` to see the prompt in action.
#### 2.7.1 `:auto_hint`
To show hint by default use `:auto_hint` option:
```ruby
prompt.expand('Overwrite Gemfile?', choices, auto_hint: true)
# =>
# Overwrite Gemfile? (enter "h" for help) [y,n,q,h]
# >> overwrite this file
```
### 2.8 collect
In order to collect more than one answer use `collect` method. Using the `key` you can describe the answers key name. All the methods for asking user input such as `ask`, `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 use [convert](#221-convert).
For example to gather some contact information do:
```ruby
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"}}
```
In order to collect _mutliple values_ for a given key in a loop, chain `values` onto the `key` desired:
```ruby
result = prompt.collect do
key(:name).ask('Name?')
key(:age).ask('Age?', convert: :int)
while prompt.yes?("continue?")
key(:addresses).values do
key(:street).ask('Street?', required: true)
key(:city).ask('City?')
key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
end
end
end
# =>
# {
# :name => "Piotr",
# :age => 30,
# :addresses => [
# {:street => "Street", :city => "City", :zip => "123"},
# {:street => "Street", :city => "City", :zip => "234"}
# ]
# }
```
### 2.9 suggest
To suggest possible matches for the user input use `suggest` method like so:
```ruby
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.
```ruby
possible = %w(status stage stash commit branch blame)
prompt.suggest('b', possible, indent: 4, single_text: 'Perhaps you meant?')
# =>
# Perhaps you meant?
# blame
```
### 2.10 slider
If you have constrained range of numbers for user to choose from you may consider using `slider`.
The slider provides easy visual way of picking a value marked by `●` symbol. You can set `:min`(defaults to 0), `:max` and `:step`(defaults to 1) options to configure slider range:
```ruby
prompt.slider('Volume', max: 100, step: 5)
# =>
# Volume ──────────●────────── 50
# (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:
```ruby
prompt.slider('Volume', max: 100, step: 5, default: 75)
# =>
# Volume ───────────────●────── 75
# (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 any `sprintf` compatible flag for number display, in our case `%d`:
```ruby
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 of `0.5` and display each value with a single decimal place use `%f` as format:
```ruby
prompt.slider("Volume", max: 10, step: 0.5, default: 5, format: "|:slider| %.1f")
# =>
# Volume |───────────────●──────| 7.5
# (Use arrow keys, press Enter to select)
```
If you wish to change the slider handle and the slider range display use `:symbols` option:
```ruby
prompt.slider("Volume", max: 100, step: 5, default: 75, symbols: {bullet: 'x', line: '_'})
# =>
# Volume _______________x______ 75%
# (Use arrow keys, press Enter to select)
```
Slider can be configured through DSL as well:
```ruby
prompt.slider('What size?') do |range|
range.max 100
range.step 5
range.default 75
range.format "|:slider| %d%"
end
# =>
# Volume |───────────────●──────| 75%
# (Use arrow keys, press Enter to select)
```
### 2.11 say
To simply print message out to standard output use `say` like so:
```ruby
prompt.say(...)
```
The `say` method also accepts option `:color` which supports all the colors provided by [pastel](https://github.com/piotrmurach/pastel#3-supported-colors)
**TTY::Prompt** provides more specific versions of `say` method to better express intention behind the message such as `ok`, `warn` and `error`.
#### 2.11.1 ok
Print message(s) in green do:
```ruby
prompt.ok(...)
```
#### 2.12.2 warn
Print message(s) in yellow do:
```ruby
prompt.warn(...)
```
#### 2.11.3 error
Print message(s) in red do:
```ruby
prompt.error(...)
```
#### 2.12 keyboard events
All the prompt types, when a key is pressed, fire key press events. You can subscribe to listen to this events by calling `on` with type of event name.
```ruby
prompt.on(:keypress) { |event| ... }
```
The event object is yielded to a block whenever particular event fires. The event has `key` and `value` methods. Further, the `key` responds to following messages:
* `name` - the name of the event such as :up, :down, letter or digit
* `meta` - true if event is non-standard key associated
* `shift` - true if shift has been pressed with the key
* `ctrl` - true if ctrl has been pressed with the key
For example, to add vim like key navigation to `select` prompt one would do the following:
```ruby
prompt.on(:keypress) do |event|
if event.value == 'j'
prompt.trigger(:keydown)
end
if event.value == 'k'
prompt.trigger(:keyup)
end
end
```
You can subscribe to more than one event:
```ruby
prompt.on(:keypress) { |key| ... }
.on(:keydown) { |key| ... }
```
The available events are:
* `:keypress`
* `:keydown`
* `:keyup`
* `:keyleft`
* `:keyright`
* `:keynum`
* `:keytab`
* `:keyenter`
* `:keyreturn`
* `:keyspace`
* `:keyescape`
* `:keydelete`
* `:keybackspace`
## 3 settings
### 3.1. `:symbols`
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:
```ruby
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| `→` | `→` |
### 3.2 `:active_color`
All prompt types support `:active_color` option. In case of `select`, `multi_select`, `enum_select` or `expand` this color is used to highlight the currently selected choice. All the resulted inputs provided by user that are read in by the prompt as answer are highlighted with this color. This option can be applied either globally for all prompts or individually.
```ruby
prompt = TTY::Prompt.new(active_color: :cyan)
```
or per individual input do:
```ruby
prompt.select('What size?', %w(Large Medium Small), active_color: :cyan)
```
Please [see pastel](https://github.com/piotrmurach/pastel#3-supported-colors) for all supported colors.
### 3.3 `:enable_color`
If you wish to disable coloring for a prompt simply pass `:enable_color` option
```ruby
prompt = TTY::Prompt.new(enable_color: true)
```
### 3.4 `:help_color`
Prompts such as `select`, `multi_select`, `expand` support `:help_color` which is used to customize the help text. This option can be applied either globally for all prompts or individually.
```ruby
prompt = TTY::Prompt.new(help_color: :cyan)
```
or per individual input do:
```ruby
prompt.select('What size?', %w(Large Medium Small), help_color: :cyan)
```
### 3.5 `:interrupt`
By default `InputInterrupt` 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` - exists with status code
* `:noop` - skips handler
* custom proc
For example, to send interrupt signal do:
```ruby
prompt = TTY::Prompt.new(interrupt: :signal)
```
### 3.6 `:prefix`
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:
```ruby
prompt = TTY::Prompt.new(prefix: '[?] ')
```
### 3.7 `:track_history`
The prompts that accept line input such as `multiline` or `ask` provide history buffer that tracks all the lines entered during `TTY::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:
```ruby
prompt = TTY::Prompt.new(track_history: false)
```
## Contributing
1. Fork it ( https://github.com/piotrmurach/tty-prompt/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
## Copyright
Copyright (c) 2015 Piotr Murach. See LICENSE for further details.
tty-prompt-0.21.0/Rakefile 0000664 0000000 0000000 00000000217 13631250432 0015375 0 ustar 00root root 0000000 0000000 require "bundler/gem_tasks"
FileList['tasks/**/*.rake'].each(&method(:import))
desc 'Run all specs'
task ci: %w[ spec ]
task default: :spec
tty-prompt-0.21.0/appveyor.yml 0000664 0000000 0000000 00000001234 13631250432 0016320 0 ustar 00root root 0000000 0000000 ---
install:
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
- gem install bundler -v '< 2.0'
- bundle install
before_test:
- ruby -v
- gem -v
- bundle -v
build: off
test_script:
- bundle exec rake ci
environment:
matrix:
- ruby_version: "200"
- ruby_version: "200-x64"
- ruby_version: "21"
- ruby_version: "21-x64"
- ruby_version: "22"
- ruby_version: "22-x64"
- ruby_version: "23"
- ruby_version: "23-x64"
- ruby_version: "24"
- ruby_version: "24-x64"
- ruby_version: "25"
- ruby_version: "25-x64"
- ruby_version: "26"
- ruby_version: "26-x64"
- ruby_version: "27"
- ruby_version: "27-x64"
tty-prompt-0.21.0/benchmarks/ 0000775 0000000 0000000 00000000000 13631250432 0016045 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/benchmarks/speed.rb 0000664 0000000 0000000 00000001314 13631250432 0017471 0 ustar 00root root 0000000 0000000 # coding: utf-8
require 'benchmark/ips'
require 'stringio'
require_relative '../lib/tty-prompt'
input = ::StringIO.new
output = ::StringIO.new
prompt = TTY::Prompt.new(input: input, output: output)
Benchmark.ips do |r|
r.report("Ruby #puts") do
output.puts "What is your name?"
end
r.report("TTY::Prompt #ask") do
prompt.ask("What is your name?")
end
end
# Calculating -------------------------------------
# Ruby #puts 34601 i/100ms
# TTY::Prompt #ask 12 i/100ms
# -------------------------------------------------
# Ruby #puts 758640.5 (±14.9%) i/s - 3736908 in 5.028562s
# TTY::Prompt #ask 63.1 (±7.9%) i/s - 324 in 5.176857s
tty-prompt-0.21.0/examples/ 0000775 0000000 0000000 00000000000 13631250432 0015546 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/examples/ask.rb 0000664 0000000 0000000 00000000226 13631250432 0016651 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask('What is your name?', default: ENV['USER'])
tty-prompt-0.21.0/examples/ask_blank.rb 0000664 0000000 0000000 00000000235 13631250432 0020020 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new(prefix: ">")
answer= prompt.ask
puts "Answer: \"#{answer}\""
tty-prompt-0.21.0/examples/ask_multiline.rb 0000664 0000000 0000000 00000000230 13631250432 0020726 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask("What\nis your\nname?", default: ENV['USER'])
tty-prompt-0.21.0/examples/ask_valid.rb 0000664 0000000 0000000 00000000461 13631250432 0020031 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask('Folder name?') do |q|
q.required(true)
q.validate ->(v) { return !Dir.exist?(v) }
q.messages[:valid?] = 'Folder already exists?'
q.messages[:required?] = 'Folder name must not be empty'
end
tty-prompt-0.21.0/examples/collect.rb 0000664 0000000 0000000 00000000632 13631250432 0017521 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'json'
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new(prefix: '[?] ')
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
puts JSON.pretty_generate(result)
tty-prompt-0.21.0/examples/echo.rb 0000664 0000000 0000000 00000000324 13631250432 0017010 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
answer = prompt.ask('Password?', echo: false) do |q|
q.validate(/^[^\.]+\.[^\.]+/)
end
puts "Password: #{answer}"
tty-prompt-0.21.0/examples/enum_select.rb 0000664 0000000 0000000 00000000327 13631250432 0020400 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
choices = %i(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt.enum_select('Select an editor', choices, default: 2)
tty-prompt-0.21.0/examples/enum_select_disabled.rb 0000664 0000000 0000000 00000000457 13631250432 0022233 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
choices = [
{name: "Emacs", disabled: '(not installed)'},
"Atom",
"GNU nano",
{name: "Notepad++", disabled: '(not installed)'},
"Sublime",
"Vim"
]
prompt.enum_select('Select an editor', choices)
tty-prompt-0.21.0/examples/enum_select_paged.rb 0000664 0000000 0000000 00000000315 13631250432 0021535 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
alfabet = ('A'..'Z').to_a
prompt.enum_select('Which letter?', alfabet, per_page: 4, cycle: true, default: 2)
tty-prompt-0.21.0/examples/enum_select_wrapped.rb 0000664 0000000 0000000 00000001730 13631250432 0022121 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative '../lib/tty-prompt'
prompt = TTY::Prompt.new
quotes = [
"There are certain queer times and occasions in this strange mixed affair we call life when a man takes this whole universe for a vast practical joke, though the wit thereof he but dimly discerns, and more than suspects that the joke is at nobody's expense but his own.",
"Talk not to me of blasphemy, man;\n I'd strike the sun if it insulted me.",
"There is a wisdom that is woe; but there is a woe that is madness. And there is a Catskill eagle in some souls that can alike dive down into the blackest gorges, and soar out of them again and become invisible in the sunny spaces. And even if he for ever flies within the gorge, that gorge is in the mountains; so that even in his lowest swoop the mountain eagle is still higher than other birds upon the plain, even though they soar."
]
answer = prompt.enum_select('Chose your quote?', quotes)
puts "Answer: #{answer}"
tty-prompt-0.21.0/examples/expand.rb 0000664 0000000 0000000 00000000772 13631250432 0017360 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
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 = TTY::Prompt.new
prompt.expand('Overwrite Gemfile?', choices, default: 3)
tty-prompt-0.21.0/examples/expand_auto.rb 0000664 0000000 0000000 00000000777 13631250432 0020415 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
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 = TTY::Prompt.new
prompt.expand('Overwrite Gemfile?', choices, auto_hint: true)
tty-prompt-0.21.0/examples/in.rb 0000664 0000000 0000000 00000000323 13631250432 0016477 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask('How do you like it on scale 1 - 10?', in: '1-10') do |q|
q.messages[:range?] = "Sorry wrong one!"
end
tty-prompt-0.21.0/examples/inputs.rb 0000664 0000000 0000000 00000000430 13631250432 0017412 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask('What is your name?', default: ENV['USER'])
prompt.yes?('Do you like Ruby?')
prompt.mask("What is your secret?")
prompt.select("Choose your destiny?", %w(Scorpion Kano Jax))
tty-prompt-0.21.0/examples/key_events.rb 0000664 0000000 0000000 00000000426 13631250432 0020251 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt::new(interrupt: :exit)
prompt.on(:keypress) do |event|
puts "name: #{event.key.name}, value: #{event.value.dump}"
end
prompt.on(:keyescape) do |event|
exit
end
prompt.read_keypress
tty-prompt-0.21.0/examples/keypress.rb 0000664 0000000 0000000 00000000270 13631250432 0017737 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt::new
answer = prompt.keypress("Press any key to continue")
puts "Answer: #{answer.inspect}"
tty-prompt-0.21.0/examples/mask.rb 0000664 0000000 0000000 00000000453 13631250432 0017030 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
require 'pastel'
prompt = TTY::Prompt.new
heart = prompt.decorate(prompt.symbols[:heart] + ' ', :magenta)
res = prompt.mask('What is your secret?', mask: heart) do |q|
q.validate(/[a-z\ ]{5,15}/)
end
puts "Secret: \"#{res}\""
tty-prompt-0.21.0/examples/multi_select.rb 0000664 0000000 0000000 00000000307 13631250432 0020564 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
drinks = %w(vodka beer wine whisky bourbon)
prompt.multi_select('Choose your favourite drink?', drinks)
tty-prompt-0.21.0/examples/multi_select_disabled.rb 0000664 0000000 0000000 00000000517 13631250432 0022416 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
drinks = [
'bourbon',
{name: 'sake', disabled: '(out of stock)'},
'vodka',
{name: 'beer', disabled: '(out of stock)'},
'wine',
'whisky'
]
answer = prompt.multi_select('Choose your favourite drink?', drinks)
puts answer.inspect
tty-prompt-0.21.0/examples/multi_select_disabled_paged.rb 0000664 0000000 0000000 00000000576 13631250432 0023563 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
numbers = [
{name: '1', disabled: 'out'},
'2',
{name: '3', disabled: 'out'},
'4',
'5',
{name: '6', disabled: 'out'},
'7',
'8',
'9',
{name: '10', disabled: 'out'}
]
answer = prompt.multi_select('Which letter?', numbers, per_page: 4, cycle: true)
puts answer.inspect
tty-prompt-0.21.0/examples/multi_select_paged.rb 0000664 0000000 0000000 00000000275 13631250432 0021730 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
alfabet = ('A'..'Z').to_a
prompt.multi_select('Which letter?', alfabet, per_page: 7, max: 3)
tty-prompt-0.21.0/examples/multi_select_wrapped.rb 0000664 0000000 0000000 00000001745 13631250432 0022315 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative '../lib/tty-prompt'
prompt = TTY::Prompt.new
quotes = [
"There are certain queer times and occasions in this strange mixed affair we call life when a man takes this whole universe for a vast practical joke, though the wit thereof he but dimly discerns, and more than suspects that the joke is at nobody's expense but his own.",
"Talk not to me of blasphemy, man; I'd strike the sun if it insulted me.",
"There is a wisdom that is woe; but there is a woe that is madness. And there is a Catskill eagle in some souls that can alike dive down into the blackest gorges, and soar out of them again and become invisible in the sunny spaces. And even if he for ever flies within the gorge, that gorge is in the mountains; so that even in his lowest swoop the mountain eagle is still higher than other birds upon the plain, even though they soar."
]
answer = prompt.multi_select('Choose your quote?', quotes, echo: false)
puts "Answer: #{answer}"
tty-prompt-0.21.0/examples/multiline.rb 0000664 0000000 0000000 00000000254 13631250432 0020076 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt::new
answer = prompt.multiline("Description:")
puts "Answer: #{answer.inspect}"
tty-prompt-0.21.0/examples/pause.rb 0000664 0000000 0000000 00000000420 13631250432 0017204 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt::new
answer = prompt.keypress("Press space or enter to continue, continuing automatically in :countdown ...", keys: [:space, :return], timeout: 3)
puts "Answer: #{answer.inspect}"
tty-prompt-0.21.0/examples/select.rb 0000664 0000000 0000000 00000000646 13631250432 0017360 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
warriors = %i(Scorpion Kano Jax Kitana Raiden)
prompt.on(:keypress) do |event|
if event.value == 'j'
prompt.trigger(:keydown)
end
if event.value == 'k'
prompt.trigger(:keyup)
end
end
prompt.on(:keyescape) do |event|
exit(1)
end
answer = prompt.select('Choose your destiny?', warriors)
puts answer.inspect
tty-prompt-0.21.0/examples/select_disabled.rb 0000664 0000000 0000000 00000000452 13631250432 0021202 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
warriors = [
'Scorpion',
'Kano',
{ name: 'Goro', disabled: '(injury)' },
'Jax',
'Kitana',
'Raiden'
]
answer = prompt.select('Choose your destiny?', warriors, enum: ')')
puts answer.inspect
tty-prompt-0.21.0/examples/select_disabled_paged.rb 0000664 0000000 0000000 00000000570 13631250432 0022343 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
numbers = [
{name: '1', disabled: 'out'},
'2',
{name: '3', disabled: 'out'},
'4',
'5',
{name: '6', disabled: 'out'},
'7',
'8',
'9',
{name: '10', disabled: 'out'}
]
answer = prompt.select('Which letter?', numbers, per_page: 4, cycle: true)
puts answer.inspect
tty-prompt-0.21.0/examples/select_enum.rb 0000664 0000000 0000000 00000000273 13631250432 0020400 0 ustar 00root root 0000000 0000000 # frozen_string_litreal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
warriors = %w(Scorpion Kano Jax)
prompt.select('Choose your destiny?', warriors, enum: ')')
tty-prompt-0.21.0/examples/select_filtered.rb 0000664 0000000 0000000 00000000353 13631250432 0021231 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
warriors = %w(Scorpion Kano Jax Kitana Raiden)
answer = prompt.select('Choose your destiny?', warriors, filter: true)
puts answer.inspect
tty-prompt-0.21.0/examples/select_paginated.rb 0000664 0000000 0000000 00000000346 13631250432 0021371 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
alfabet = ('A'..'Z').to_a
answer = prompt.select('Which letter?', alfabet, per_page: 7, cycle: true, default: 5)
puts answer.inspect
tty-prompt-0.21.0/examples/select_wrapped.rb 0000664 0000000 0000000 00000001723 13631250432 0021077 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative '../lib/tty-prompt'
prompt = TTY::Prompt.new
quotes = [
"There are certain queer times and occasions in this strange mixed affair we call life when a man takes this whole universe for a vast practical joke, though the wit thereof he but dimly discerns, and more than suspects that the joke is at nobody's expense but his own.",
"Talk not to me of blasphemy, man;\n I'd strike the sun if it insulted me.",
"There is a wisdom that is woe; but there is a woe that is madness. And there is a Catskill eagle in some souls that can alike dive down into the blackest gorges, and soar out of them again and become invisible in the sunny spaces. And even if he for ever flies within the gorge, that gorge is in the mountains; so that even in his lowest swoop the mountain eagle is still higher than other birds upon the plain, even though they soar."
]
answer = prompt.select('Chose your quote?', quotes)
puts "Answer: #{answer}"
tty-prompt-0.21.0/examples/slider.rb 0000664 0000000 0000000 00000000260 13631250432 0017353 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.slider("Volume", max: 100, step: 5, default: 75, format: "|:slider| %d%%")
tty-prompt-0.21.0/examples/validation.rb 0000664 0000000 0000000 00000000257 13631250432 0020231 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.ask('What is your username?') do |q|
q.validate(/^[^\.]+\.[^\.]+/)
end
tty-prompt-0.21.0/examples/yes_no.rb 0000664 0000000 0000000 00000000200 13631250432 0017357 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "../lib/tty-prompt"
prompt = TTY::Prompt.new
prompt.yes?('Do you like Ruby?')
tty-prompt-0.21.0/lib/ 0000775 0000000 0000000 00000000000 13631250432 0014476 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/lib/tty-prompt.rb 0000664 0000000 0000000 00000000101 13631250432 0017152 0 ustar 00root root 0000000 0000000 require_relative 'tty/prompt'
require_relative 'tty/test_prompt'
tty-prompt-0.21.0/lib/tty/ 0000775 0000000 0000000 00000000000 13631250432 0015316 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/lib/tty/prompt.rb 0000664 0000000 0000000 00000035766 13631250432 0017205 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'forwardable'
require 'pastel'
require 'tty-cursor'
require 'tty-reader'
require 'tty-screen'
require_relative 'prompt/answers_collector'
require_relative 'prompt/confirm_question'
require_relative 'prompt/expander'
require_relative 'prompt/enum_list'
require_relative 'prompt/keypress'
require_relative 'prompt/list'
require_relative 'prompt/multi_list'
require_relative 'prompt/multiline'
require_relative 'prompt/mask_question'
require_relative 'prompt/question'
require_relative 'prompt/slider'
require_relative 'prompt/statement'
require_relative 'prompt/suggestion'
require_relative 'prompt/symbols'
require_relative 'prompt/utils'
require_relative 'prompt/version'
module TTY
# A main entry for asking prompt questions.
class Prompt
extend Forwardable
# Raised when wrong parameter is used to configure prompt
ConfigurationError = Class.new(StandardError)
# Raised when type conversion cannot be performed
ConversionError = Class.new(StandardError)
# Raised when the passed in validation argument is of wrong type
ValidationCoercion = Class.new(TypeError)
# Raised when the required argument is not supplied
ArgumentRequired = Class.new(ArgumentError)
# Raised when the argument validation fails
ArgumentValidation = Class.new(ArgumentError)
# Raised when the argument is not expected
InvalidArgument = Class.new(ArgumentError)
# @api private
attr_reader :input
# @api private
attr_reader :output
attr_reader :reader
attr_reader :cursor
# Prompt prefix
#
# @example
# prompt = TTY::Prompt.new(prefix: [?])
#
# @return [String]
#
# @api private
attr_reader :prefix
# Theme colors
#
# @api private
attr_reader :active_color, :help_color, :error_color, :enabled_color
# The collection of display symbols
#
# @example
# prompt = TTY::Prompt.new(symbols: {marker: '>'})
#
# @return [Hash]
#
# @api private
attr_reader :symbols
def_delegators :@pastel, :decorate, :strip
def_delegators :@cursor, :clear_lines, :clear_line,
:show, :hide
def_delegators :@reader, :read_char, :read_keypress, # :read_line,
:read_multiline, :on, :subscribe, :unsubscribe, :trigger,
:count_screen_lines
def_delegators :@output, :print, :puts, :flush
def self.messages
{
range?: 'Value %{value} must be within the range %{in}',
valid?: 'Your answer is invalid (must match %{valid})',
required?: 'Value must be provided'
}
end
# This fixes Forwardable module keyword arguments warning
def read_line(message, **options)
@reader.read_line(message, **options)
end
# Initialize a Prompt
#
# @param [Hash] options
# @option options [IO] :input
# the input stream
# @option options [IO] :output
# the output stream
# @option options [Hash] :env
# the environment variables
# @option options [String] :prefix
# the prompt prefix, by default empty
# @option options [Boolean] :enable_color
# enable color support, true by default
# @option options [String] :active_color
# the color used for selected option
# @option options [String] :help_color
# the color used for help text
# @option options [String] :error_color
# the color used for displaying error messages
# @option options [Symbol] :interrupt
# handling of Ctrl+C key out of :signal, :exit, :noop
# @option options [Boolean] :track_history
# disable line history tracking, true by default
# @option options [Hash] :symbols
# the symbols displayed in prompts such as :marker, :cross
#
# @api public
def initialize(*args)
options = Utils.extract_options!(args)
@input = options.fetch(:input) { $stdin }
@output = options.fetch(:output) { $stdout }
@env = options.fetch(:env) { ENV }
@prefix = options.fetch(:prefix) { '' }
@enabled_color = options[:enable_color]
@active_color = options.fetch(:active_color) { :green }
@help_color = options.fetch(:help_color) { :bright_black }
@error_color = options.fetch(:error_color) { :red }
@interrupt = options.fetch(:interrupt) { :error }
@track_history = options.fetch(:track_history) { true }
@symbols = Symbols.symbols.merge(options.fetch(:symbols, {}))
@cursor = TTY::Cursor
@pastel = Pastel.new(@enabled_color.nil? ? {} : { enabled: @enabled_color })
@reader = TTY::Reader.new(
input: @input,
output: @output,
interrupt: @interrupt,
track_history: @track_history,
env: @env
)
end
# Invoke a question type of prompt
#
# @example
# prompt = TTY::Prompt.new
# prompt.invoke_question(Question, "Your name? ")
#
# @return [String]
#
# @api public
def invoke_question(object, message, **options, &block)
options[:messages] = self.class.messages
question = object.new(self, **options)
question.(message, &block)
end
# Ask a question.
#
# @example
# propmt = TTY::Prompt.new
# prompt.ask("What is your name?")
#
# @param [String] message
# the question to be asked
#
# @yieldparam [TTY::Prompt::Question] question
# further configure the question
#
# @yield [question]
#
# @return [TTY::Prompt::Question]
#
# @api public
def ask(message = '', **options, &block)
invoke_question(Question, message, **options, &block)
end
# Ask a question with a keypress answer
#
# @see #ask
#
# @api public
def keypress(message = '', **options, &block)
invoke_question(Keypress, message, **options, &block)
end
# Ask a question with a multiline answer
#
# @example
# prompt.multiline('Description?')
#
# @return [Array[String]]
#
# @api public
def multiline(message = '', **options, &block)
invoke_question(Multiline, message, **options, &block)
end
# Invoke a list type of prompt
#
# @example
# prompt = TTY::Prompt.new
# editors = %w(emacs nano vim)
# prompt.invoke_select(EnumList, "Select editor: ", editors)
#
# @return [String]
#
# @api public
def invoke_select(object, question, *args, &block)
options = Utils.extract_options!(args)
choices = if block
[]
elsif args.empty?
possible = options.dup
options = {}
possible
elsif args.size == 1 && args[0].is_a?(Hash)
Utils.extract_options!(args)
else
args.flatten
end
list = object.new(self, **options)
list.(question, choices, &block)
end
# Ask masked question
#
# @example
# propmt = TTY::Prompt.new
# prompt.mask("What is your secret?")
#
# @return [TTY::Prompt::MaskQuestion]
#
# @api public
def mask(message = '', **options, &block)
invoke_question(MaskQuestion, message, **options, &block)
end
# Ask a question with a list of options
#
# @example
# prompt = TTY::Prompt.new
# prompt.select("What size?", %w(large medium small))
#
# @example
# prompt = TTY::Prompt.new
# prompt.select("What size?") do |menu|
# menu.choice :large
# menu.choices %w(:medium :small)
# end
#
# @param [String] question
# the question to ask
#
# @param [Array[Object]] choices
# the choices to select from
#
# @api public
def select(question, *args, &block)
invoke_select(List, question, *args, &block)
end
# Ask a question with multiple attributes activated
#
# @example
# prompt = TTY::Prompt.new
# choices = %w(Scorpion Jax Kitana Baraka Jade)
# prompt.multi_select("Choose your destiny?", choices)
#
# @param [String] question
# the question to ask
#
# @param [Array[Object]] choices
# the choices to select from
#
# @return [String]
#
# @api public
def multi_select(question, *args, &block)
invoke_select(MultiList, question, *args, &block)
end
# Ask a question with indexed list
#
# @example
# prompt = TTY::Prompt.new
# editors = %w(emacs nano vim)
# prompt.enum_select(EnumList, "Select editor: ", editors)
#
# @param [String] question
# the question to ask
#
# @param [Array[Object]] choices
# the choices to select from
#
# @return [String]
#
# @api public
def enum_select(question, *args, &block)
invoke_select(EnumList, question, *args, &block)
end
# A shortcut method to ask the user positive question and return
# true for 'yes' reply, false for 'no'.
#
# @example
# prompt = TTY::Prompt.new
# prompt.yes?('Are you human?')
# # => Are you human? (Y/n)
#
# @return [Boolean]
#
# @api public
def yes?(message, *args, &block)
defaults = { default: true }
options = Utils.extract_options!(args)
options.merge!(defaults.reject { |k, _| options.key?(k) })
question = ConfirmQuestion.new(self, **options)
question.call(message, &block)
end
# A shortcut method to ask the user negative question and return
# true for 'no' reply.
#
# @example
# prompt = TTY::Prompt.new
# prompt.no?('Are you alien?') # => true
# # => Are you human? (y/N)
#
# @return [Boolean]
#
# @api public
def no?(message, *args, &block)
defaults = { default: false }
options = Utils.extract_options!(args)
options.merge!(defaults.reject { |k, _| options.key?(k) })
question = ConfirmQuestion.new(self, **options)
!question.call(message, &block)
end
# Expand available options
#
# @example
# prompt = TTY::Prompt.new
# choices = [{
# key: 'Y',
# name: 'Overwrite',
# value: :yes
# }, {
# key: 'n',
# name: 'Skip',
# value: :no
# }]
# prompt.expand('Overwirte Gemfile?', choices)
#
# @return [Object]
# the user specified value
#
# @api public
def expand(message, *args, &block)
invoke_select(Expander, message, *args, &block)
end
# Ask a question with a range slider
#
# @example
# prompt = TTY::Prompt.new
# prompt.slider('What size?', min: 32, max: 54, step: 2)
#
# @param [String] question
# the question to ask
#
# @return [String]
#
# @api public
def slider(question, *args, &block)
options = Utils.extract_options!(args)
slider = Slider.new(self, **options)
slider.call(question, &block)
end
# Print statement out. If the supplied message ends with a space or
# tab character, a new line will not be appended.
#
# @example
# say("Simple things.", color: :red)
#
# @param [String] message
#
# @return [String]
#
# @api public
def say(message = '', options = {})
message = message.to_s
return if message.empty?
statement = Statement.new(self, options)
statement.call(message)
end
# Print statement(s) out in red green.
#
# @example
# prompt.ok "Are you sure?"
# prompt.ok "All is fine!", "This is fine too."
#
# @param [Array] messages
#
# @return [Array] messages
#
# @api public
def ok(*args)
options = Utils.extract_options!(args)
args.each { |message| say message, options.merge(color: :green) }
end
# Print statement(s) out in yellow color.
#
# @example
# prompt.warn "This action can have dire consequences"
# prompt.warn "Carefull young apprentice", "This is potentially dangerous"
#
# @param [Array] messages
#
# @return [Array] messages
#
# @api public
def warn(*args)
options = Utils.extract_options!(args)
args.each { |message| say message, options.merge(color: :yellow) }
end
# Print statement(s) out in red color.
#
# @example
# prompt.error "Shutting down all systems!"
# prompt.error "Nothing is fine!", "All is broken!"
#
# @param [Array] messages
#
# @return [Array] messages
#
# @api public
def error(*args)
options = Utils.extract_options!(args)
args.each { |message| say message, options.merge(color: :red) }
end
# Print debug information in terminal top right corner
#
# @example
# prompt.debug "info1", "info2"
#
# @param [Array] messages
#
# @retrun [nil]
#
# @api public
def debug(*messages)
longest = messages.max_by(&:length).size
width = TTY::Screen.width - longest
print cursor.save
messages.reverse_each.with_index do |msg, i|
print cursor.column(width) + cursor.up + cursor.clear_line_after
print msg
end
ensure
print cursor.restore
end
# Takes the string provided by the user and compare it with other possible
# matches to suggest an unambigous string
#
# @example
# prompt.suggest('sta', ['status', 'stage', 'commit', 'branch'])
# # => "status, stage"
#
# @param [String] message
#
# @param [Array] possibilities
#
# @param [Hash] options
# @option options [String] :indent
# The number of spaces for indentation
# @option options [String] :single_text
# The text for a single suggestion
# @option options [String] :plural_text
# The text for multiple suggestions
#
# @return [String]
#
# @api public
def suggest(message, possibilities, **options)
suggestion = Suggestion.new(**options)
say(suggestion.suggest(message, possibilities))
end
# Gathers more than one aswer
#
# @example
# prompt.collect do
# key(:name).ask('Name?')
# end
#
# @return [Hash]
# the collection of answers
#
# @api public
def collect(**options, &block)
collector = AnswersCollector.new(self, **options)
collector.call(&block)
end
# Check if outputing to terminal
#
# @return [Boolean]
#
# @api public
def tty?
stdout.tty?
end
# Return standard in
#
# @api private
def stdin
$stdin
end
# Return standard out
#
# @api private
def stdout
$stdout
end
# Return standard error
#
# @api private
def stderr
$stderr
end
# Inspect class name and public attributes
# @return [String]
#
# @api public
def inspect
attributes = {
input: input,
output: output,
prefix: prefix,
active_color: active_color,
error_color: error_color,
enabled_color: enabled_color,
help_color: help_color
}
"#<#{self.class}: #{attributes.each { |name, val| "@#{name}=#{val}" }}"
end
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/ 0000775 0000000 0000000 00000000000 13631250432 0016637 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/lib/tty/prompt/answers_collector.rb 0000664 0000000 0000000 00000003210 13631250432 0022710 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class AnswersCollector
# Initialize answer collector
#
# @api public
def initialize(prompt, **options)
@prompt = prompt
@answers = options.fetch(:answers) { {} }
end
# Start gathering answers
#
# @return [Hash]
# the collection of all answers
#
# @api public
def call(&block)
instance_eval(&block)
@answers
end
# Create answer entry
#
# @example
# key(:name).ask('Name?')
#
# @api public
def key(name, &block)
@name = name
if block
answer = create_collector.call(&block)
add_answer(answer)
end
self
end
# Change to collect all values for a key
#
# @example
# key(:colors).values.ask('Color?')
#
# @api public
def values(&block)
@answers[@name] = Array(@answers[@name])
if block
answer = create_collector.call(&block)
add_answer(answer)
end
self
end
# @api public
def create_collector
self.class.new(@prompt)
end
# @api public
def add_answer(answer)
if @answers[@name].is_a?(Array)
@answers[@name] << answer
else
@answers[@name] = answer
end
end
private
# @api private
def method_missing(method, *args, **options, &block)
answer = @prompt.public_send(method, *args, **options, &block)
add_answer(answer)
end
end # AnswersCollector
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/block_paginator.rb 0000664 0000000 0000000 00000003367 13631250432 0022333 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'paginator'
module TTY
class Prompt
class BlockPaginator < Paginator
# Paginate list of choices based on current active choice.
# Move entire pages.
#
# @api public
def paginate(list, active, per_page = nil, &block)
default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
@per_page = @per_page || per_page || default_size
check_page_size!
# Don't paginate short lists
if list.size <= @per_page
@start_index = 0
@end_index = list.size - 1
if block
return list.each_with_index(&block)
else
return list.each_with_index.to_enum
end
end
unless active.nil? # User may input index out of range
@last_index = active
end
page = (@last_index / @per_page.to_f).ceil
pages = (list.size / @per_page.to_f).ceil
if page == 0
@start_index = 0
@end_index = @start_index + @per_page - 1
elsif page > 0 && page < pages
@start_index = (page - 1) * @per_page
@end_index = @start_index + @per_page - 1
elsif page == pages
@start_index = (page - 1) * @per_page
@end_index = list.size - 1
else
@end_index = list.size - 1
@start_index = @end_index - @per_page + 1
end
sliced_list = list[@start_index..@end_index]
page_range = (@start_index..@end_index)
return sliced_list.zip(page_range).to_enum unless block_given?
sliced_list.each_with_index do |item, index|
block[item, @start_index + index]
end
end
end # EnumPaginator
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/choice.rb 0000664 0000000 0000000 00000005552 13631250432 0020425 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
# An immutable representation of a single choice option from select menu
#
# @api public
class Choice
# Create choice from value
#
# @examples
# Choice.from(:foo)
# # =>
#
# Choice.from([:foo, 1])
# # =>
#
# Choice.from({name: :foo, value: 1, key: 'f'}
# # =>
#
# @param [Object] val
# the value to be converted
#
# @raise [ArgumentError]
#
# @return [Choice]
#
# @api public
def self.from(val)
case val
when Choice
val
when Array
name, value, options = *val
if name.is_a?(Hash)
convert_hash(name)
else
new(name.to_s, (value.nil? ? name.to_s : value), **(options || {}))
end
when Hash
convert_hash(val)
else
new(val, val)
end
end
# Convert hash into choice
#
# @api public
def self.convert_hash(val)
if val.key?(:name) && val.key?(:value)
new(val[:name].to_s, val[:value], **val)
elsif val.key?(:name)
new(val[:name].to_s, val[:name].to_s, **val)
else
new(val.keys.first.to_s, val.values.first)
end
end
# The label name
#
# @api public
attr_reader :name
# The keyboard key to activate this choice
#
# @api public
attr_reader :key
# The text to display for disabled choice
#
# @api public
attr_reader :disabled
# Create a Choice instance
#
# @api public
def initialize(name, value, **options)
@name = name
@value = value
@key = options[:key]
@disabled = options[:disabled].nil? ? false : options[:disabled]
freeze
end
# Check if this choice is disabled
#
# @return [Boolean]
#
# @api public
def disabled?
!!@disabled
end
# Read value and evaluate
#
# @api public
def value
case @value
when Proc
@value.call
else
@value
end
end
# Object equality comparison
#
# @return [Boolean]
#
# @api public
def ==(other)
return false unless other.is_a?(self.class)
name == other.name &&
value == other.value &&
key == other.key
end
# Object string representation
#
# @return [String]
#
# @api public
def to_s
"#{name}"
end
end # Choice
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/choices.rb 0000664 0000000 0000000 00000004445 13631250432 0020610 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'forwardable'
require_relative 'choice'
module TTY
class Prompt
# A class responsible for storing a collection of choices
#
# @api private
class Choices
include Enumerable
extend Forwardable
# The actual collection choices
#
# @return [Array[Choice]]
#
# @api public
attr_reader :choices
def_delegators :choices, :length, :size, :to_ary, :empty?,
:values_at, :index
# Convenience for creating choices
#
# @param [Array[Object]] choices
# the choice objects
#
# @return [Choices]
# the choices collection
#
# @api public
def self.[](*choices)
new(choices)
end
# Create Choices collection
#
# @param [Array[Choice]] choices
# the choices to add to collection
#
# @api public
def initialize(choices = [])
@choices = choices.map do |choice|
Choice.from(choice)
end
end
# Iterate over all choices in the collection
#
# @yield [Choice]
#
# @api public
def each(&block)
return to_enum unless block_given?
choices.each(&block)
end
# Add choice to collection
#
# @param [Object] choice
# the choice to add
#
# @api public
def <<(choice)
choices << Choice.from(choice)
end
# Access choice by index
#
# @param [Integer] index
#
# @return [Choice]
#
# @api public
def [](index)
@choices[index]
end
# Pluck a choice by its name from collection
#
# @param [String] name
# the label name for the choice
#
# @return [Choice]
#
# @api public
def pluck(name)
map { |choice| choice.public_send(name) }
end
# Find a matching choice
#
# @exmaple
# choices.find_by(:name, 'small')
#
# @param [Symbol] attr
# the attribute name
# @param [Object] value
#
# @return [Choice]
#
# @api public
def find_by(attr, value)
find { |choice| choice.public_send(attr) == value }
end
end # Choices
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/confirm_question.rb 0000664 0000000 0000000 00000007066 13631250432 0022561 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'question'
require_relative 'utils'
module TTY
class Prompt
class ConfirmQuestion < Question
# Create confirmation question
#
# @param [Hash] options
# @option options [String] :suffix
# @option options [String] :positive
# @option options [String] :negative
#
# @api public
def initialize(prompt, **options)
super
@suffix = options.fetch(:suffix) { UndefinedSetting }
@positive = options.fetch(:positive) { UndefinedSetting }
@negative = options.fetch(:negative) { UndefinedSetting }
end
def positive?
@positive != UndefinedSetting
end
def negative?
@negative != UndefinedSetting
end
def suffix?
@suffix != UndefinedSetting
end
# Set question suffix
#
# @api public
def suffix(value = (not_set = true))
return @negative if not_set
@suffix = value
end
# Set value for matching positive choice
#
# @api public
def positive(value = (not_set = true))
return @positive if not_set
@positive = value
end
# Set value for matching negative choice
#
# @api public
def negative(value = (not_set = true))
return @negative if not_set
@negative = value
end
def call(message, &block)
return if Utils.blank?(message)
@message = message
block.call(self) if block
setup_defaults
render
end
# Render confirmation question
#
# @return [String]
#
# @api private
def render_question
header = "#{@prefix}#{message} "
if !@done
header += @prompt.decorate("(#{@suffix})", @help_color) + ' '
else
answer = convert_result(@input)
label = answer ? @positive : @negative
header += @prompt.decorate(label, @active_color)
end
header << "\n" if @done
header
end
protected
# Decide how to handle input from user
#
# @api private
def process_input(question)
@input = read_input(question)
if Utils.blank?(@input)
@input = default ? positive : negative
end
@evaluator.call(@input)
end
# @api private
def setup_defaults
@convert = conversion
return if suffix? && positive?
if suffix? && (!positive? || !negative?)
parts = @suffix.split('/')
@positive = parts[0]
@negative = parts[1]
elsif !suffix? && positive?
@suffix = create_suffix
else
create_default_labels
end
end
# @api private
def create_default_labels
@suffix = default ? 'Y/n' : 'y/N'
@positive = default ? 'Yes' : 'yes'
@negative = default ? 'no' : 'No'
@validation = /^(y(es)?|no?)$/i
@messages[:valid?] = "Invalid input."
end
# @api private
def create_suffix
(default ? positive.capitalize : positive.downcase) + '/' +
(default ? negative.downcase : negative.capitalize)
end
# Create custom conversion
#
# @api private
def conversion
proc { |input|
positive_word = Regexp.escape(positive)
positive_letter = Regexp.escape(positive[0])
pattern = Regexp.new("^#{positive_word}|#{positive_letter}$", true)
!input.match(pattern).nil?
}
end
end # ConfirmQuestion
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/converter_dsl.rb 0000664 0000000 0000000 00000000730 13631250432 0022035 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'converter_registry'
module TTY
class Prompt
module ConverterDSL
def converter_registry
@converter_registry ||= ConverterRegistry.new
end
def converter(name, &block)
@converter_registry = converter_registry.register(name, &block)
self
end
def convert(name, data)
@converter_registry[name, data]
end
end # ConverterDSL
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/converter_registry.rb 0000664 0000000 0000000 00000002626 13631250432 0023131 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
# Immutable collection of converters for type transformation
#
# @api private
class ConverterRegistry
# Create a registry of conversions
#
# @param [Hash] registry
#
# @api private
def initialize(registry = {})
@_registry = registry.dup.freeze
freeze
end
# Register converter
#
# @param [Symbol] name
# the converter name
#
# @api public
def register(name, contents = nil, &block)
item = block_given? ? block : contents
if key?(name)
raise ArgumentError,
"Converter for #{name.inspect} already registered"
end
self.class.new(@_registry.merge(name => item))
end
# Check if converter is registered
#
# @return [Boolean]
#
# @api public
def key?(key)
@_registry.key?(key)
end
# Execute converter
#
# @api public
def call(name, input)
if name.respond_to?(:call)
converter = name
else
converter = @_registry.fetch(name) do
raise ArgumentError, "#{name.inspect} is not registered"
end
end
converter[input]
end
alias [] call
def inspect
@_registry.inspect
end
end # ConverterRegistry
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/converters.rb 0000664 0000000 0000000 00000003340 13631250432 0021356 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'pathname'
require 'necromancer'
require_relative 'converter_dsl'
module TTY
class Prompt
module Converters
extend ConverterDSL
# Delegate Necromancer errors
#
# @api private
def self.on_error
if block_given?
yield
else
raise ArgumentError, 'You need to provide a block argument.'
end
rescue Necromancer::ConversionTypeError => e
raise ConversionError, e.message
end
converter(:bool) do |input|
on_error { Necromancer.convert(input).to(:boolean, strict: true) }
end
converter(:string) do |input|
String(input).chomp
end
converter(:symbol) do |input|
input.to_sym
end
converter(:date) do |input|
on_error { Necromancer.convert(input).to(:date, strict: true) }
end
converter(:datetime) do |input|
on_error { Necromancer.convert(input).to(:datetime, strict: true) }
end
converter(:int) do |input|
on_error { Necromancer.convert(input).to(:integer, strict: true) }
end
converter(:float) do |input|
on_error { Necromancer.convert(input).to(:float, strict: true) }
end
converter(:range) do |input|
on_error { Necromancer.convert(input).to(:range, strict: true) }
end
converter(:regexp) do |input|
Regexp.new(input)
end
converter(:file) do |input|
::File.open(::File.join(Dir.pwd, input))
end
converter(:path) do |input|
Pathname.new(::File.join(Dir.pwd, input))
end
converter(:char) do |input|
String(input).chars.to_a[0]
end
end # Converters
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/distance.rb 0000664 0000000 0000000 00000003137 13631250432 0020762 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
# A class responsible for string comparison
class Distance
# Calculate the optimal string alignment distance
#
# @api public
def distance(first, second)
distances = []
rows = first.to_s.length
cols = second.to_s.length
0.upto(rows) do |index|
distances << [index] + [0] * cols
end
distances[0] = 0.upto(cols).to_a
1.upto(rows) do |first_index|
1.upto(cols) do |second_index|
first_char = first[first_index - 1]
second_char = second[second_index - 1]
cost = first_char == second_char ? 0 : 1
distances[first_index][second_index] = [
distances[first_index - 1][second_index], # deletion
distances[first_index][second_index - 1], # insertion
distances[first_index - 1][second_index - 1] # substitution
].min + cost
if first_index > 1 && second_index > 1
first_previous_char = first[first_index - 2]
second_previous_char = second[second_index - 2]
if first_char == second_previous_char && second_char == first_previous_char
distances[first_index][second_index] = [
distances[first_index][second_index],
distances[first_index - 2][second_index - 2] + 1 # transposition
].min
end
end
end
end
distances[rows][cols]
end
end # Distance
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/enum_list.rb 0000664 0000000 0000000 00000024076 13631250432 0021174 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'English'
require_relative 'choices'
require_relative 'block_paginator'
require_relative 'paginator'
module TTY
class Prompt
# A class reponsible for rendering enumerated list menu.
# Used by {Prompt} to display static choice menu.
#
# @api private
class EnumList
PAGE_HELP = '(Press tab/right or left to reveal more choices)'
# Create instance of EnumList menu.
#
# @api public
def initialize(prompt, **options)
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@enum = options.fetch(:enum) { ')' }
@default = options.fetch(:default) { -1 }
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@error_color = options.fetch(:error_color) { @prompt.error_color }
@cycle = options.fetch(:cycle) { false }
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
@input = nil
@done = false
@first_render = true
@failure = false
@active = @default
@choices = Choices.new
@per_page = options[:per_page]
@page_help = options[:page_help] || PAGE_HELP
@paginator = BlockPaginator.new
@page_active = @default
end
# Change symbols used by this prompt
#
# @param [Hash] new_symbols
# the new symbols to use
#
# @api public
def symbols(new_symbols = (not_set = true))
return @symbols if not_set
@symbols.merge!(new_symbols)
end
# Set default option selected
#
# @api public
def default(default)
@default = default
end
# Check if default value is set
#
# @return [Boolean]
#
# @api public
def default?
@default > 0
end
# Set number of items per page
#
# @api public
def per_page(value)
@per_page = value
end
def page_size
(@per_page || Paginator::DEFAULT_PAGE_SIZE)
end
# Check if list is paginated
#
# @return [Boolean]
#
# @api private
def paginated?
@choices.size > page_size
end
# @param [String] text
# the help text to display per page
# @api pbulic
def page_help(text)
@page_help = text
end
# Set selecting active index using number pad
#
# @api public
def enum(value)
@enum = value
end
# Add a single choice
#
# @api public
def choice(*value, &block)
if block
@choices << (value << block)
else
@choices << value
end
end
# Add multiple choices
#
# @param [Array[Object]] values
# the values to add as choices
#
# @api public
def choices(values = (not_set = true))
if not_set
@choices
else
values.each { |val| @choices << val }
end
end
# Call the list menu by passing question and choices
#
# @param [String] question
#
# @param
# @api public
def call(question, possibilities, &block)
choices(possibilities)
@question = question
block[self] if block
setup_defaults
@prompt.subscribe(self) do
render
end
end
def keypress(event)
if %i[backspace delete].include?(event.key.name)
return if @input.empty?
@input.chop!
mark_choice_as_active
elsif event.value =~ /^\d+$/
@input += event.value
mark_choice_as_active
end
end
def keyreturn(*)
@failure = false
num = @input.to_i
choice_disabled = choices[num - 1] && choices[num - 1].disabled?
choice_in_range = num > 0 && num <= @choices.size
if choice_in_range && !choice_disabled || @input.empty?
@done = true
else
@input = ''
@failure = true
end
end
alias keyenter keyreturn
def keyright(*)
if (@page_active + page_size) <= @choices.size
@page_active += page_size
elsif @cycle
@page_active = 1
end
end
alias keytab keyright
def keyleft(*)
if (@page_active - page_size) >= 0
@page_active -= page_size
elsif @cycle
@page_active = @choices.size - 1
end
end
private
# Find active choice or set to default
#
# @return [nil]
#
# @api private
def mark_choice_as_active
next_active = @choices[@input.to_i - 1]
if next_active && next_active.disabled?
# noop
elsif (@input.to_i > 0) && next_active
@active = @input.to_i
else
@active = @default
end
@page_active = @active
end
# Validate default indexes to be within range
#
# @api private
def validate_defaults
msg = if @default.nil? || @default.to_s.empty?
"default index must be an integer in range (1 - #{choices.size})"
elsif @default < 1 || @default > @choices.size
"default index #{@default} out of range (1 - #{@choices.size})"
elsif choices[@default - 1] && choices[@default - 1].disabled?
"default index #{@default} matches disabled choice item"
end
raise(ConfigurationError, msg) if msg
end
# Setup default option and active selection
#
# @api private
def setup_defaults
if !default?
@default = (0..choices.length).find {|i| !choices[i].disabled? } + 1
end
validate_defaults
mark_choice_as_active
end
# Render a selection list.
#
# By default the result is printed out.
#
# @return [Object] value
# return the selected value
#
# @api private
def render
@input = ''
until @done
question = render_question
@prompt.print(question)
@prompt.print(render_error) if @failure
if paginated? && !@done
@prompt.print(render_page_help)
end
@prompt.read_keypress
question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
@prompt.print(refresh(question_lines_count(question_lines)))
end
@prompt.print(render_question)
answer
end
# Count how many screen lines the question spans
#
# @return [Integer]
#
# @api private
def question_lines_count(question_lines)
question_lines.reduce(0) do |acc, line|
acc + @prompt.count_screen_lines(line)
end
end
# Find value for the choice selected
#
# @return [nil, Object]
#
# @api private
def answer
@choices[@active - 1].value
end
# Determine area of the screen to clear
#
# @param [Integer] lines
# the lines to clear
#
# @return [String]
#
# @api private
def refresh(lines)
@prompt.clear_lines(lines) +
@prompt.cursor.clear_screen_down
end
# Render question with the menu options
#
# @return [String]
#
# @api private
def render_question
header = ["#{@prefix}#{@question} #{render_header}\n"]
unless @done
header << render_menu
header << render_footer
end
header.join
end
# Error message when incorrect index chosen
#
# @api private
def error_message
error = 'Please enter a valid number'
"\n" + @prompt.decorate('>>', @error_color) + ' ' + error
end
# Render error message and return cursor to position of input
#
# @return [String]
#
# @api private
def render_error
error = error_message.dup
if !paginated?
error << @prompt.cursor.prev_line
error << @prompt.cursor.forward(render_footer.size)
end
error
end
# Render chosen option
#
# @return [String]
#
# @api private
def render_header
return '' unless @done
return '' unless @active
selected_item = @choices[@active - 1].name.to_s
@prompt.decorate(selected_item, @active_color)
end
# Render footer for the indexed menu
#
# @return [String]
#
# @api private
def render_footer
" Choose 1-#{@choices.size} [#{@default}]: #{@input}"
end
# Pagination help message
#
# @return [String]
#
# @api private
def page_help_message
return '' unless paginated?
"\n" + @prompt.decorate(@page_help, @help_color)
end
# Render page help
#
# @return [String]
#
# @api private
def render_page_help
help = page_help_message.dup
if @failure
help << @prompt.cursor.prev_line
end
help << @prompt.cursor.prev_line
help << @prompt.cursor.forward(render_footer.size)
end
# Render menu with indexed choices to select from
#
# @return [String]
#
# @api private
def render_menu
output = []
@paginator.paginate(@choices, @page_active, @per_page) do |choice, index|
num = (index + 1).to_s + @enum + ' '
selected = num.to_s + choice.name.to_s
output << if index + 1 == @active && !choice.disabled?
(' ' * 2) + @prompt.decorate(selected, @active_color)
elsif choice.disabled?
@prompt.decorate(@symbols[:cross], :red) + ' ' +
selected + ' ' + choice.disabled.to_s
else
(' ' * 2) + selected
end
output << "\n"
end
output.join
end
end # EnumList
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/evaluator.rb 0000664 0000000 0000000 00000001202 13631250432 0021161 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'result'
module TTY
class Prompt
# Evaluates provided parameters and stops if any of them fails
# @api private
class Evaluator
attr_reader :results
def initialize(question, &block)
@question = question
@results = []
instance_eval(&block) if block
end
def call(initial)
seed = Result::Success.new(@question, initial)
results.reduce(seed, &:with)
end
def check(proc = nil, &block)
results << (proc || block)
end
alias_method :<<, :check
end # Evaluator
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/expander.rb 0000664 0000000 0000000 00000016632 13631250432 0021002 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'choices'
module TTY
class Prompt
# A class responsible for rendering expanding options
# Used by {Prompt} to display key options question.
#
# @api private
class Expander
HELP_CHOICE = {
key: 'h',
name: 'print help',
value: :help
}.freeze
# Create instance of Expander
#
# @api public
def initialize(prompt, options = {})
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@default = options.fetch(:default) { 1 }
@auto_hint = options.fetch(:auto_hint) { false }
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@choices = Choices.new
@selected = nil
@done = false
@status = :collapsed
@hint = nil
@default_key = false
end
def expanded?
@status == :expanded
end
def collapsed?
@status == :collapsed
end
def expand
@status = :expanded
end
# Respond to submit event
#
# @api public
def keyenter(_)
if @input.nil? || @input.empty?
@input = @choices[@default - 1].key
@default_key = true
end
selected = select_choice(@input)
if selected && selected.key.to_s == 'h'
expand
@selected = nil
@input = ''
elsif selected
@done = true
@selected = selected
@hint = nil
else
@input = ''
end
end
alias keyreturn keyenter
# Respond to key press event
#
# @api public
def keypress(event)
if [:backspace, :delete].include?(event.key.name)
@input.chop! unless @input.empty?
elsif event.value =~ /^[^\e\n\r]/
@input += event.value
end
@selected = select_choice(@input)
if @selected && !@default_key && collapsed?
@hint = @selected.name
end
end
# Select choice by given key
#
# @return [Choice]
#
# @api private
def select_choice(key)
@choices.find_by(:key, key)
end
# Set default value.
#
# @api public
def default(value = (not_set = true))
return @default if not_set
@default = value
end
# Add a single choice
#
# @api public
def choice(value, &block)
if block
@choices << value.update(value: block)
else
@choices << value
end
end
# Add multiple choices
#
# @param [Array[Object]] values
# the values to add as choices
#
# @api public
def choices(values)
values.each { |val| choice(val) }
end
# Execute this prompt
#
# @api public
def call(message, possibilities, &block)
choices(possibilities)
@message = message
block.call(self) if block
setup_defaults
choice(HELP_CHOICE)
@prompt.subscribe(self) do
render
end
end
private
# Create possible keys with current choice highlighted
#
# @return [String]
#
# @api private
def possible_keys
keys = @choices.pluck(:key)
default_key = keys[@default - 1]
if @selected
index = keys.index(@selected.key)
keys[index] = @prompt.decorate(keys[index], @active_color)
elsif @input.to_s.empty? && default_key
keys[@default - 1] = @prompt.decorate(default_key, @active_color)
end
keys.join(',')
end
# @api private
def render
@input = ''
until @done
question = render_question
@prompt.print(question)
read_input
@prompt.print(refresh(question.lines.count))
end
@prompt.print(render_question)
answer
end
# @api private
def answer
@selected.value
end
# Render message with options
#
# @return [String]
#
# @api private
def render_header
header = ["#{@prefix}#{@message} "]
if @done
selected_item = @selected.name.to_s
header << @prompt.decorate(selected_item, @active_color)
elsif collapsed?
header << %[(enter "h" for help) ]
header << "[#{possible_keys}] "
header << @input
end
header.join
end
# Show hint for selected option key
#
# return [String]
#
# @api private
def render_hint
"\n" + @prompt.decorate('>> ', @active_color) +
@hint +
@prompt.cursor.prev_line +
@prompt.cursor.forward(@prompt.strip(render_header).size)
end
# Render question with menu
#
# @return [String]
#
# @api private
def render_question
load_auto_hint if @auto_hint
header = render_header
header << render_hint if @hint
header << "\n" if @done
if !@done && expanded?
header << render_menu
header << render_footer
end
header
end
def load_auto_hint
if @hint.nil? && collapsed?
if @selected
@hint = @selected.name
else
if @input.empty?
@hint = @choices[@default - 1].name
else
@hint = "invalid option"
end
end
end
end
def render_footer
" Choice [#{@choices[@default - 1].key}]: #{@input}"
end
def read_input
@prompt.read_keypress
end
# Refresh the current input
#
# @param [Integer] lines
#
# @return [String]
#
# @api private
def refresh(lines)
if (@hint && (!@selected || @done)) || (@auto_hint && collapsed?)
@hint = nil
@prompt.clear_lines(lines, :down) +
@prompt.cursor.prev_line
elsif expanded?
@prompt.clear_lines(lines)
else
@prompt.clear_line
end
end
# Render help menu
#
# @api private
def render_menu
output = ["\n"]
@choices.each do |choice|
chosen = %(#{choice.key} - #{choice.name})
if @selected && @selected.key == choice.key
chosen = @prompt.decorate(chosen, @active_color)
end
output << ' ' + chosen + "\n"
end
output.join
end
def setup_defaults
validate_choices
end
def validate_choices
errors = []
keys = []
@choices.each do |choice|
if choice.key.nil?
errors << "Choice #{choice.name} is missing a :key attribute"
next
end
if choice.key.length != 1
errors << "Choice key `#{choice.key}` is more than one character long."
end
if choice.key.to_s == 'h'
errors << "Choice key `#{choice.key}` is reserved for help menu."
end
if keys.include?(choice.key)
errors << "Choice key `#{choice.key}` is a duplicate."
end
keys << choice.key if choice.key
end
errors.each { |err| raise ConfigurationError, err }
end
end # Expander
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/keypress.rb 0000664 0000000 0000000 00000004560 13631250432 0021036 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'question'
require_relative 'timer'
module TTY
class Prompt
class Keypress < Question
# Create keypress question
#
# @param [Prompt] prompt
# @param [Hash] options
#
# @api public
def initialize(prompt, **options)
super
@echo = options.fetch(:echo) { false }
@keys = options.fetch(:keys) { UndefinedSetting }
@timeout = options.fetch(:timeout) { UndefinedSetting }
@interval = options.fetch(:interval) {
(@timeout != UndefinedSetting && @timeout < 1) ? @timeout : 1
}
@decimals = (@interval.to_s.split('.')[1] || []).size
@countdown = @timeout
time = timeout? ? Float(@timeout) : nil
@timer = Timer.new(time, Float(@interval))
@prompt.subscribe(self)
end
def countdown(value = (not_set = true))
return @countdown if not_set
@countdown = value
end
# Check if any specific keys are set
def any_key?
@keys == UndefinedSetting
end
# Check if timeout is set
def timeout?
@timeout != UndefinedSetting
end
def keypress(event)
if any_key?
@done = true
elsif @keys.is_a?(Array) && @keys.include?(event.key.name)
@done = true
else
@done = false
end
end
def render_question
header = super
if timeout?
header.gsub!(/:countdown/, format("%.#{@decimals}f", countdown))
end
header
end
def interval_handler(time)
return if @done
question = render_question
line_size = question.size
total_lines = @prompt.count_screen_lines(line_size)
@prompt.print(refresh(question.lines.count, total_lines))
countdown(time)
@prompt.print(render_question)
end
def process_input(question)
@prompt.print(render_question)
@timer.on_tick do |time|
interval_handler(time)
end
@timer.while_remaining do |remaining|
break if @done
@input = @prompt.read_keypress(nonblock: true)
end
countdown(0) unless @done
@evaluator.(@input)
end
def refresh(lines, lines_to_clear)
@prompt.clear_lines(lines)
end
end # Keypress
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/list.rb 0000664 0000000 0000000 00000034120 13631250432 0020137 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'English'
require_relative 'choices'
require_relative 'paginator'
require_relative 'block_paginator'
module TTY
class Prompt
# A class responsible for rendering select list menu
# Used by {Prompt} to display interactive menu.
#
# @api private
class List
HELP = '(Use %s arrow%s keys, press Enter to select%s)'
# Allowed keys for filter, along with backspace and canc.
FILTER_KEYS_MATCHER = /\A([[:alnum:]]|[[:punct:]])\Z/.freeze
# Create instance of TTY::Prompt::List menu.
#
# @param Hash options
# the configuration options
# @option options [Symbol] :default
# the default active choice, defaults to 1
# @option options [Symbol] :color
# the color for the selected item, defualts to :green
# @option options [Symbol] :marker
# the marker for the selected item
# @option options [String] :enum
# the delimiter for the item index
#
# @api public
def initialize(prompt, **options)
check_options_consistency(options)
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@enum = options.fetch(:enum) { nil }
@default = Array(options[:default])
@choices = Choices.new
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@cycle = options.fetch(:cycle) { false }
@filterable = options.fetch(:filter) { false }
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
@filter = []
@filter_cache = {}
@help = options[:help]
@first_render = true
@done = false
@per_page = options[:per_page]
@paginator = Paginator.new
@block_paginator = BlockPaginator.new
@by_page = false
@paging_changed = false
end
# Change symbols used by this prompt
#
# @param [Hash] new_symbols
# the new symbols to use
#
# @api public
def symbols(new_symbols = (not_set = true))
return @symbols if not_set
@symbols.merge!(new_symbols)
end
# Set default option selected
#
# @api public
def default(*default_values)
@default = default_values
end
# Select paginator based on the current navigation key
#
# @return [Paginator]
#
# @api private
def paginator
@by_page ? @block_paginator : @paginator
end
# Synchronize paginators start positions
#
# @api private
def sync_paginators
if @by_page
if @paginator.start_index
@block_paginator.reset!
@block_paginator.start_index = @paginator.start_index
end
else
if @block_paginator.start_index
@paginator.reset!
@paginator.start_index = @block_paginator.start_index
end
end
end
# Set number of items per page
#
# @api public
def per_page(value)
@per_page = value
end
def page_size
(@per_page || Paginator::DEFAULT_PAGE_SIZE)
end
# Check if list is paginated
#
# @return [Boolean]
#
# @api private
def paginated?
choices.size > page_size
end
# Provide help information
#
# @param [String] value
# the new help text
#
# @return [String]
#
# @api public
def help(value = (not_set = true))
return @help if !@help.nil? && not_set
@help = (@help.nil? && !not_set) ? value : default_help
end
# Information about arrow keys
#
# @return [String]
#
# @api private
def arrows_help
up_down = @symbols[:arrow_up] + "/" + @symbols[:arrow_down]
left_right = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
arrows = [up_down]
arrows << " and " if paginated?
arrows << left_right if paginated?
arrows.join
end
# Default help text
#
# @api public
def default_help
# Note that enumeration and filter are mutually exclusive
tokens = if enumerate?
[" or number (1-#{choices.size})", '']
elsif filterable?
['', ', and letter keys to filter']
else
['', '']
end
format(self.class::HELP, arrows_help, *tokens)
end
# Set selecting active index using number pad
#
# @api public
def enum(value)
@enum = value
end
# Add a single choice
#
# @api public
def choice(*value, &block)
@filter_cache = {}
if block
@choices << (value << block)
else
@choices << value
end
end
# Add multiple choices, or return them.
#
# @param [Array[Object]] values
# the values to add as choices; if not passed, the current
# choices are displayed.
#
# @api public
def choices(values = (not_set = true))
if not_set
if !filterable? || @filter.empty?
@choices
else
filter_value = @filter.join.downcase
@filter_cache[filter_value] ||= @choices.select do |choice|
!choice.disabled? &&
choice.name.downcase.include?(filter_value)
end
end
else
@filter_cache = {}
values.each { |val| @choices << val }
end
end
# Call the list menu by passing question and choices
#
# @param [String] question
#
# @param
# @api public
def call(question, possibilities, &block)
choices(possibilities)
@question = question
block.call(self) if block
setup_defaults
@prompt.subscribe(self) do
render
end
end
# Check if list is enumerated
#
# @return [Boolean]
def enumerate?
!@enum.nil?
end
def keynum(event)
return unless enumerate?
value = event.value.to_i
return unless (1..choices.count).cover?(value)
return if choices[value - 1].disabled?
@active = value
end
def keyenter(*)
@done = true unless choices.empty?
end
alias keyreturn keyenter
alias keyspace keyenter
def search_choice_in(searchable)
searchable.find { |i| !choices[i - 1].disabled? }
end
def keyup(*)
searchable = (@active - 1).downto(1).to_a
prev_active = search_choice_in(searchable)
if prev_active
@active = prev_active
elsif @cycle
searchable = choices.length.downto(1).to_a
prev_active = search_choice_in(searchable)
@active = prev_active if prev_active
end
@paging_changed = @by_page
@by_page = false
end
def keydown(*)
searchable = ((@active + 1)..choices.length)
next_active = search_choice_in(searchable)
if next_active
@active = next_active
elsif @cycle
searchable = (1..choices.length)
next_active = search_choice_in(searchable)
@active = next_active if next_active
end
@paging_changed = @by_page
@by_page = false
end
alias keytab keydown
# Moves all choices page by page keeping the current selected item
# at the same level on each page.
#
# When the choice on a page is outside of next page range then
# adjust it to the last item, otherwise leave unchanged.
def keyright(*)
if (@active + page_size) <= @choices.size
searchable = ((@active + page_size)..choices.length)
@active = search_choice_in(searchable)
elsif @active <= @choices.size # last page shorter
current = @active % page_size
remaining = @choices.size % page_size
if current.zero? || (remaining > 0 && current > remaining)
searchable = @choices.size.downto(0).to_a
@active = search_choice_in(searchable)
elsif @cycle
searchable = ((current.zero? ? page_size : current)..choices.length)
@active = search_choice_in(searchable)
end
end
@paging_changed = !@by_page
@by_page = true
end
alias keypage_down keyright
def keyleft(*)
if (@active - page_size) > 0
searchable = ((@active - page_size)..choices.length)
@active = search_choice_in(searchable)
elsif @cycle
searchable = @choices.size.downto(1).to_a
@active = search_choice_in(searchable)
end
@paging_changed = !@by_page
@by_page = true
end
alias keypage_up keyleft
def keypress(event)
return unless filterable?
if event.value =~ FILTER_KEYS_MATCHER
@filter << event.value
@active = 1
end
end
def keydelete(*)
return unless filterable?
@filter.clear
@active = 1
end
def keybackspace(*)
return unless filterable?
@filter.pop
@active = 1
end
private
def check_options_consistency(options)
if options.key?(:enum) && options.key?(:filter)
raise ConfigurationError,
"Enumeration can't be used with filter"
end
end
# Setup default option and active selection
#
# @api private
def setup_defaults
validate_defaults
if !@default.empty?
@active = @default.first
else
@active = @choices.index { |choice| !choice.disabled? } + 1
end
end
# Validate default indexes to be within range
#
# @raise [ConfigurationError]
# raised when the default index is either non-integer,
# out of range or clashes with disabled choice item.
#
# @api private
def validate_defaults
@default.each do |d|
msg = if d.nil? || d.to_s.empty?
"default index must be an integer in range (1 - #{choices.size})"
elsif d < 1 || d > choices.size
"default index `#{d}` out of range (1 - #{choices.size})"
elsif choices[d - 1] && choices[d - 1].disabled?
"default index `#{d}` matches disabled choice item"
end
raise(ConfigurationError, msg) if msg
end
end
# Render a selection list.
#
# By default the result is printed out.
#
# @return [Object] value
# return the selected value
#
# @api private
def render
@prompt.print(@prompt.hide)
until @done
question = render_question
@prompt.print(question)
@prompt.read_keypress
# Split manually; if the second line is blank (when there are no
# matching lines), it won't be included by using String#lines.
question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
@prompt.print(refresh(question_lines_count(question_lines)))
end
@prompt.print(render_question)
answer
ensure
@prompt.print(@prompt.show)
end
# Count how many screen lines the question spans
#
# @return [Integer]
#
# @api private
def question_lines_count(question_lines)
question_lines.reduce(0) do |acc, line|
acc + @prompt.count_screen_lines(line)
end
end
# Find value for the choice selected
#
# @return [nil, Object]
#
# @api private
def answer
choices[@active - 1].value
end
# Clear screen lines
#
# @param [String]
#
# @api private
def refresh(lines)
@prompt.clear_lines(lines)
end
# Render question with instructions and menu
#
# @return [String]
#
# @api private
def render_question
header = ["#{@prefix}#{@question} #{render_header}\n"]
@first_render = false
unless @done
header << render_menu
end
header.join
end
# Is filtering enabled?
#
# @return [Boolean]
#
# @api private
def filterable?
@filterable
end
# Header part showing the current filter
#
# @return String
#
# @api private
def filter_help
"(Filter: #{@filter.join.inspect})"
end
# Render initial help and selected choice
#
# @return [String]
#
# @api private
def render_header
if @done
selected_item = choices[@active - 1].name
@prompt.decorate(selected_item.to_s, @active_color)
elsif @first_render
@prompt.decorate(help, @help_color)
elsif filterable? && @filter.any?
@prompt.decorate(filter_help, @help_color)
end
end
# Render menu with choices to select from
#
# @return [String]
#
# @api private
def render_menu
output = []
sync_paginators if @paging_changed
paginator.paginate(choices, @active, @per_page) do |choice, index|
num = enumerate? ? (index + 1).to_s + @enum + ' ' : ''
message = if index + 1 == @active && !choice.disabled?
selected = "#{@symbols[:marker]} #{num}#{choice.name}"
@prompt.decorate(selected.to_s, @active_color)
elsif choice.disabled?
@prompt.decorate(@symbols[:cross], :red) +
" #{num}#{choice.name} #{choice.disabled}"
else
" #{num}#{choice.name}"
end
end_index = paginated? ? paginator.end_index : choices.size - 1
newline = (index == end_index) ? '' : "\n"
output << (message + newline)
end
output.join
end
end # List
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/mask_question.rb 0000664 0000000 0000000 00000004332 13631250432 0022050 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'question'
module TTY
class Prompt
class MaskQuestion < Question
# Create masked question
#
# @param [Hash] options
# @option options [String] :mask
#
# @api public
def initialize(prompt, **options)
super
@mask = options.fetch(:mask) { @prompt.symbols[:dot] }
@done_masked = false
@failure = false
end
# Set character for masking the STDIN input
#
# @param [String] char
#
# @return [self]
#
# @api public
def mask(char = (not_set = true))
return @mask if not_set
@mask = char
end
def keyreturn(event)
@done_masked = true
end
def keyenter(event)
@done_masked = true
end
def keypress(event)
if [:backspace, :delete].include?(event.key.name)
@input.chop! unless @input.empty?
elsif event.value =~ /^[^\e\n\r]/
@input += event.value
end
end
# Render question and input replaced with masked character
#
# @api private
def render_question
header = ["#{@prefix}#{message} "]
if echo?
masked = @mask.to_s * @input.to_s.length
if @done_masked && !@failure
masked = @prompt.decorate(masked, @active_color)
elsif @done_masked && @failure
masked = @prompt.decorate(masked, @error_color)
end
header << masked
end
header << "\n" if @done
header.join
end
def render_error(errors)
@failure = !errors.empty?
super
end
# Read input from user masked by character
#
# @private
def read_input(question)
@done_masked = false
@failure = false
@input = ''
@prompt.print(question)
until @done_masked
@prompt.read_keypress
question = render_question
total_lines = @prompt.count_screen_lines(question)
@prompt.print(@prompt.clear_lines(total_lines))
@prompt.print(render_question)
end
@prompt.puts
@input
end
end # MaskQuestion
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/multi_list.rb 0000664 0000000 0000000 00000011035 13631250432 0021351 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'list'
module TTY
class Prompt
# A class responsible for rendering multi select list menu.
# Used by {Prompt} to display interactive choice menu.
#
# @api private
class MultiList < List
HELP = '(Use %s arrow%s keys, press Space to select and Enter to finish%s)'
# Create instance of TTY::Prompt::MultiList menu.
#
# @param [Prompt] :prompt
# @param [Hash] options
#
# @api public
def initialize(prompt, **options)
super
@selected = []
@help = options[:help]
@echo = options.fetch(:echo, true)
@min = options[:min]
@max = options[:max]
end
# Set a minimum number of choices
#
# @api public
def min(value)
@min = value
end
# Set a maximum number of choices
#
# @api public
def max(value)
@max = value
end
# Callback fired when enter/return key is pressed
#
# @api private
def keyenter(*)
if @min
super if @selected.size >= @min
else
super
end
end
alias keyreturn keyenter
# Callback fired when space key is pressed
#
# @api private
def keyspace(*)
active_choice = choices[@active - 1]
if @selected.include?(active_choice)
@selected.delete(active_choice)
else
return if @max && @selected.size >= @max
@selected << active_choice
end
end
private
# Setup default options and active selection
#
# @api private
def setup_defaults
validate_defaults
# At this stage, @choices matches all the visible choices.
@selected = @choices.values_at(*@default.map { |d| d - 1 })
if !@default.empty?
@active = @default.last
else
@active = @choices.index { |choice| !choice.disabled? } + 1
end
end
# Generate selected items names
#
# @return [String]
#
# @api private
def selected_names
@selected.map(&:name).join(', ')
end
# Header part showing the minimum/maximum number of choices
#
# @return [String]
#
# @api private
def minmax_help
help = []
help << "min. #{@min}" if @min
help << "max. #{@max}" if @max
"(%s) " % [ help.join(' ') ]
end
# Render initial help text and then currently selected choices
#
# @api private
def render_header
instructions = @prompt.decorate(help, @help_color)
minmax_suffix = @min || @max ? minmax_help : ""
if @done && @echo
@prompt.decorate(selected_names, @active_color)
elsif @selected.size.nonzero? && @echo
help_suffix = filterable? && @filter.any? ? " #{filter_help}" : ""
minmax_suffix + selected_names +
(@first_render ? " #{instructions}" : help_suffix)
elsif @first_render
minmax_suffix + instructions
elsif filterable? && @filter.any?
minmax_suffix + filter_help
elsif @min || @max
minmax_help
end
end
# All values for the choices selected
#
# @return [Array[nil,Object]]
#
# @api private
def answer
@selected.map(&:value)
end
# Render menu with choices to select from
#
# @return [String]
#
# @api private
def render_menu
output = []
sync_paginators if @paging_changed
paginator.paginate(choices, @active, @per_page) do |choice, index|
num = enumerate? ? (index + 1).to_s + @enum + ' ' : ''
indicator = (index + 1 == @active) ? @symbols[:marker] : ' '
indicator += ' '
message = if @selected.include?(choice) && !choice.disabled?
selected = @prompt.decorate(@symbols[:radio_on], @active_color)
"#{selected} #{num}#{choice.name}"
elsif choice.disabled?
@prompt.decorate(@symbols[:cross], :red) +
" #{num}#{choice.name} #{choice.disabled}"
else
"#{@symbols[:radio_off]} #{num}#{choice.name}"
end
end_index = paginated? ? paginator.end_index : choices.size - 1
newline = (index == end_index) ? '' : "\n"
output << indicator + message + newline
end
output.join
end
end # MultiList
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/multiline.rb 0000664 0000000 0000000 00000003175 13631250432 0021174 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'question'
require_relative 'symbols'
module TTY
class Prompt
# A prompt responsible for multi line user input
#
# @api private
class Multiline < Question
HELP = '(Press CTRL-D or CTRL-Z to finish)'.freeze
def initialize(prompt, **options)
super
@help = options[:help] || self.class::HELP
@first_render = true
@lines_count = 0
end
# Provide help information
#
# @return [String]
#
# @api public
def help(value = (not_set = true))
return @help if not_set
@help = value
end
def read_input
@prompt.read_multiline
end
def keyreturn(*)
@lines_count += 1
end
alias keyenter keyreturn
def render_question
header = ["#{@prefix}#{message} "]
if !echo?
header
elsif @done
header << @prompt.decorate("#{@input}", @active_color)
elsif @first_render
header << @prompt.decorate(help, @help_color)
@first_render = false
end
header << "\n"
header.join
end
def process_input(question)
@prompt.print(question)
@lines = read_input
@input = "#{@lines.first.strip} ..." unless @lines.first.to_s.empty?
if Utils.blank?(@input)
@input = default? ? default : nil
end
@evaluator.(@lines)
end
def refresh(lines, lines_to_clear)
size = @lines_count + lines_to_clear + 1
@prompt.clear_lines(size)
end
end # Multiline
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/paginator.rb 0000664 0000000 0000000 00000006332 13631250432 0021154 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class Paginator
DEFAULT_PAGE_SIZE = 6
# The 0-based index of the first item on this page
attr_accessor :start_index
# The 0-based index of the last item on this page
attr_reader :end_index
# The 0-based index of the active item on this page
attr_reader :current_index
# The 0-based index of the previously active item on this page
attr_reader :last_index
# Create a Paginator
#
# @api private
def initialize(**options)
@last_index = Array(options[:default]).flatten.first || 0
@per_page = options[:per_page]
@start_index = Array(options[:default]).flatten.first
end
# Reset current page indexes
#
# @api private
def reset!
@start_index = nil
@end_index = nil
end
# Check if page size is valid
#
# @raise [InvalidArgument]
#
# @api private
def check_page_size!
raise InvalidArgument, 'per_page must be > 0' if @per_page < 1
end
# Paginate collection given an active index
#
# @param [Array[Choice]] list
# a collection of choice items
# @param [Integer] active
# current choice active index
# @param [Integer] per_page
# number of choice items per page
#
# @return [Enumerable]
# the list between start and end index
#
# @api public
def paginate(list, active, per_page = nil, &block)
current_index = active - 1
default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
@per_page = @per_page || per_page || default_size
check_page_size!
@start_index ||= (current_index / @per_page) * @per_page
@end_index ||= @start_index + @per_page - 1
# Don't paginate short lists
if list.size <= @per_page
@start_index = 0
@end_index = list.size - 1
if block
return list.each_with_index(&block)
else
return list.each_with_index.to_enum
end
end
step = (current_index - @last_index).abs
if current_index > @last_index # going up
if current_index >= @end_index && current_index < list.size - 1
last_page = list.size - @per_page
@start_index = [@start_index + step, last_page].min
end
elsif current_index < @last_index # going down
if current_index <= @start_index && current_index > 0
@start_index = [@start_index - step, 0].max
end
end
# Cycle list
if current_index.zero?
@start_index = 0
elsif current_index == list.size - 1
@start_index = list.size - 1 - (@per_page - 1)
end
@end_index = @start_index + (@per_page - 1)
@last_index = current_index
sliced_list = list[@start_index..@end_index]
page_range = (@start_index..@end_index)
return sliced_list.zip(page_range).to_enum unless block_given?
sliced_list.each_with_index do |item, index|
block[item, @start_index + index]
end
end
end # Paginator
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/question.rb 0000664 0000000 0000000 00000021505 13631250432 0021036 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'converters'
require_relative 'evaluator'
require_relative 'question/modifier'
require_relative 'question/validation'
require_relative 'question/checks'
require_relative 'utils'
module TTY
# A class responsible for shell prompt interactions.
class Prompt
# A class responsible for gathering user input
#
# @api public
class Question
include Checks
UndefinedSetting = Class.new do
def to_s
"undefined"
end
alias_method :inspect, :to_s
end
# Store question message
# @api public
attr_reader :message
attr_reader :modifier
attr_reader :validation
# Initialize a Question
#
# @api public
def initialize(prompt, **options)
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@default = options.fetch(:default) { UndefinedSetting }
@required = options.fetch(:required) { false }
@echo = options.fetch(:echo) { true }
@in = options.fetch(:in) { UndefinedSetting }
@modifier = options.fetch(:modifier) { [] }
@validation = options.fetch(:validation) { UndefinedSetting }
@convert = options.fetch(:convert) { UndefinedSetting }
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@error_color = options.fetch(:error_color) { :red }
@value = options.fetch(:value) { UndefinedSetting }
@messages = Utils.deep_copy(options.fetch(:messages) { { } })
@done = false
@first_render = true
@input = nil
@evaluator = Evaluator.new(self)
@evaluator << CheckRequired
@evaluator << CheckDefault
@evaluator << CheckRange
@evaluator << CheckValidation
@evaluator << CheckModifier
end
# Stores all the error messages displayed to user
# The currently supported messages are:
# * :range?
# * :required?
# * :valid?
attr_reader :messages
# Retrieve message based on the key
#
# @param [Symbol] name
# the name of message key
#
# @param [Hash] tokens
# the tokens to evaluate
#
# @return [Array[String]]
#
# @api private
def message_for(name, tokens = nil)
template = @messages[name]
if template && !template.match(/\%\{/).nil?
[template % tokens]
else
[template || '']
end
end
# Call the question
#
# @param [String] message
#
# @return [self]
#
# @api public
def call(message = '', &block)
@message = message
block.call(self) if block
@prompt.subscribe(self) do
render
end
end
# Read answer and convert to type
#
# @api private
def render
@errors = []
until @done
result = process_input(render_question)
if result.failure?
@errors = result.errors
@prompt.print(render_error(result.errors))
else
@done = true
end
question = render_question
input_line = question + result.value.to_s
total_lines = @prompt.count_screen_lines(input_line)
@prompt.print(refresh(question.lines.count, total_lines))
end
@prompt.print(render_question)
convert_result(result.value)
end
# Render question
#
# @return [String]
#
# @api private
def render_question
header = []
if !Utils.blank?(@prefix) || !Utils.blank?(message)
header << "#{@prefix}#{message} "
end
if !echo?
header
elsif @done
header << @prompt.decorate(@input.to_s, @active_color)
elsif default? && !Utils.blank?(@default)
header << @prompt.decorate("(#{default})", @help_color) + ' '
end
header << "\n" if @done
header.join
end
# Decide how to handle input from user
#
# @api private
def process_input(question)
@input = read_input(question)
if Utils.blank?(@input)
@input = default? ? default : nil
end
@evaluator.(@input)
end
# Process input
#
# @api private
def read_input(question)
options = {echo: echo}
if value? && @first_render
options[:value] = @value
@first_render = false
end
@prompt.read_line(question, **options).chomp
end
# Handle error condition
#
# @return [String]
#
# @api private
def render_error(errors)
errors.reduce([]) do |acc, err|
acc << @prompt.decorate('>>', :red) + ' ' + err
acc
end.join("\n")
end
# Determine area of the screen to clear
#
# @param [Integer] lines
# number of lines to clear
#
# @return [String]
#
# @api private
def refresh(lines, lines_to_clear)
output = []
if @done
if @errors.count.zero?
output << @prompt.cursor.up(lines)
else
lines += @errors.count
lines_to_clear += @errors.count
end
else
output << @prompt.cursor.up(lines)
end
output.join + @prompt.clear_lines(lines_to_clear)
end
# Convert value to expected type
#
# @param [Object] value
#
# @api private
def convert_result(value)
if convert? & !Utils.blank?(value)
Converters.convert(@convert, value)
else
value
end
end
# Specify answer conversion
#
# @api public
def convert(value)
@convert = value
end
# Check if conversion is set
#
# @return [Boolean]
#
# @api public
def convert?
@convert != UndefinedSetting
end
# Set default value.
#
# @api public
def default(value = (not_set = true))
return @default if not_set
@default = value
end
# Check if default value is set
#
# @return [Boolean]
#
# @api public
def default?
@default != UndefinedSetting
end
# Ensure that passed argument is present or not
#
# @return [Boolean]
#
# @api public
def required(value = (not_set = true), message = nil)
messages[:required?] = message if message
return @required if not_set
@required = value
end
alias_method :required?, :required
# Set validation rule for an argument
#
# @param [Object] value
#
# @return [Question]
#
# @api public
def validate(value = nil, message = nil, &block)
messages[:valid?] = message if message
@validation = (value || block)
end
# Prepopulate input with custom content
#
# @api public
def value(val)
return @value if val.nil?
@value = val
end
# Check if custom value is present
#
# @api private
def value?
@value != UndefinedSetting
end
def validation?
@validation != UndefinedSetting
end
# Modify string according to the rule given.
#
# @param [Symbol] rule
#
# @api public
def modify(*rules)
@modifier = rules
end
# Turn terminal echo on or off. This is used to secure the display so
# that the entered characters are not echoed back to the screen.
#
# @api public
def echo(value = nil)
return @echo if value.nil?
@echo = value
end
alias_method :echo?, :echo
# Turn raw mode on or off. This enables character-based input.
#
# @api public
def raw(value = nil)
return @raw if value.nil?
@raw = value
end
alias_method :raw?, :raw
# Set expected range of values
#
# @param [String] value
#
# @api public
def in(value = (not_set = true), message = nil)
messages[:range?] = message if message
if in? && !@in.is_a?(Range)
@in = Converters.convert(:range, @in)
end
return @in if not_set
@in = Converters.convert(:range, value)
end
# Check if range is set
#
# @return [Boolean]
#
# @api public
def in?
@in != UndefinedSetting
end
# @api public
def to_s
message.to_s
end
# String representation of this question
# @api public
def inspect
"#<#{self.class.name} @message=#{message}, @input=#{@input}>"
end
end # Question
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/question/ 0000775 0000000 0000000 00000000000 13631250432 0020506 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/lib/tty/prompt/question/checks.rb 0000664 0000000 0000000 00000004512 13631250432 0022275 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class Question
module Checks
# Check if modifications are applicable
class CheckModifier
def self.call(question, value)
if !question.modifier.nil? || question.modifier
[Modifier.new(question.modifier).apply_to(value)]
else
[value]
end
end
end
# Check if value is within range
class CheckRange
def self.float?(value)
!/[-+]?(\d*[.])?\d+/.match(value.to_s).nil?
end
def self.int?(value)
!/^[-+]?\d+$/.match(value.to_s).nil?
end
def self.cast(value)
if float?(value)
value.to_f
elsif int?(value)
value.to_i
else
value
end
end
def self.call(question, value)
if !question.in? ||
(question.in? && question.in.include?(cast(value)))
[value]
else
tokens = {value: value, in: question.in}
[value, question.message_for(:range?, tokens)]
end
end
end
# Check if input requires validation
class CheckValidation
def self.call(question, value)
if !question.validation? || (question.required? && value.nil?) ||
(question.validation? &&
Validation.new(question.validation).call(value))
[value]
else
tokens = {valid: question.validation.inspect}
[value, question.message_for(:valid?, tokens)]
end
end
end
# Check if default value provided
class CheckDefault
def self.call(question, value)
if value.nil? && question.default?
[question.default]
else
[value]
end
end
end
# Check if input is required
class CheckRequired
def self.call(question, value)
if question.required? && !question.default? && value.nil?
[value, question.message_for(:required?)]
else
[value]
end
end
end
end # Checks
end # Question
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/question/modifier.rb 0000664 0000000 0000000 00000005507 13631250432 0022640 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class Question
# A class representing String modifications.
class Modifier
attr_reader :modifiers
# Initialize a Modifier
#
# @api public
def initialize(modifiers)
@modifiers = modifiers
end
# Change supplied value according to the given string transformation.
# Valid settings are:
#
# @param [String] value
# the string to be modified
#
# @return [String]
#
# @api private
def apply_to(value)
modifiers.reduce(value) do |result, mod|
result = Modifier.letter_case(mod, result)
Modifier.whitespace(mod, result)
end
end
# Changes letter casing in a string according to valid modifications.
# For invalid modification option the string is preserved.
#
# @param [Symbol] mod
# the modification to change the string
#
# @option mod [Symbol] :up change to upper case
# @option mod [Symbol] :upcase change to upper case
# @option mod [Symbol] :uppercase change to upper case
# @option mod [Symbol] :down change to lower case
# @option mod [Symbol] :downcase change to lower case
# @option mod [Symbol] :capitalize change all words to start
# with uppercase case letter
#
# @return [String]
#
# @api public
def self.letter_case(mod, value)
return value unless value.is_a?(String)
case mod
when :up, :upcase, :uppercase
value.upcase
when :down, :downcase, :lowercase
value.downcase
when :capitalize
value.capitalize
else
value
end
end
# Changes whitespace in a string according to valid modifications.
#
# @param [Symbol] mod
# the modification to change the string
#
# @option mod [String] :trim, :strip
# remove whitespace for the start and end
# @option mod [String] :chomp remove record separator from the end
# @option mod [String] :collapse remove any duplicate whitespace
# @option mod [String] :remove remove all whitespace
#
# @api public
def self.whitespace(mod, value)
return value unless value.is_a?(String)
case mod
when :trim, :strip
value.strip
when :chomp
value.chomp
when :collapse
value.gsub(/\s+/, ' ')
when :remove
value.gsub(/\s+/, '')
else
value
end
end
end # Modifier
end # Question
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/question/validation.rb 0000664 0000000 0000000 00000003526 13631250432 0023173 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class Question
# A class representing question validation.
class Validation
# Available validator names
VALIDATORS = {
email: /^[a-z0-9._%+-]+@([a-z0-9-]+\.)+[a-z]{2,6}$/i
}.freeze
attr_reader :pattern
# Initialize a Validation
#
# @param [Object] pattern
#
# @return [undefined]
#
# @api private
def initialize(pattern)
@pattern = coerce(pattern)
end
# Convert validation into known type.
#
# @param [Object] pattern
#
# @raise [TTY::ValidationCoercion]
# raised when failed to convert validation
#
# @api private
def coerce(pattern)
case pattern
when String, Symbol, Proc
pattern
when Regexp
Regexp.new(pattern.to_s)
else
raise ValidationCoercion, "Wrong type, got #{pattern.class}"
end
end
# Test if the input passes the validation
#
# @example
# Validation.new(/pattern/)
# validation.call(input) # => true
#
# @param [Object] input
# the input to validate
#
# @return [Boolean]
#
# @api public
def call(input)
if pattern.is_a?(String) || pattern.is_a?(Symbol)
VALIDATORS.key?(pattern.to_sym)
!VALIDATORS[pattern.to_sym].match(input.to_s).nil?
elsif pattern.is_a?(Regexp)
!pattern.match(input.to_s).nil?
elsif pattern.is_a?(Proc)
result = pattern.call(input.to_s)
result.nil? ? false : result
else false
end
end
end # Validation
end # Question
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/result.rb 0000664 0000000 0000000 00000001602 13631250432 0020501 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
# Accumulates errors
class Result
attr_reader :question, :value, :errors
def initialize(question, value, errors = [])
@question = question
@value = value
@errors = errors
end
def with(condition = nil, &block)
validator = (condition || block)
(new_value, validation_error) = validator.call(question, value)
accumulated_errors = errors + Array(validation_error)
if accumulated_errors.empty?
Success.new(question, new_value)
else
Failure.new(question, new_value, accumulated_errors)
end
end
def success?
is_a?(Success)
end
def failure?
is_a?(Failure)
end
class Success < Result
end
class Failure < Result
end
end
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/slider.rb 0000664 0000000 0000000 00000011326 13631250432 0020451 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
# A class responsible for shell prompt interactions.
class Prompt
# A class responsible for gathering numeric input from range
#
# @api public
class Slider
HELP = '(Use arrow keys, press Enter to select)'.freeze
FORMAT = ':slider %d'.freeze
# Initailize a Slider
#
# @param [Prompt] prompt
# the prompt
# @param [Hash] options
# the options to configure this slider
# @option options [Integer] :min The minimum value
# @option options [Integer] :max The maximum value
# @option options [Integer] :step The step value
# @option options [String] :format The display format
#
# @api public
def initialize(prompt, **options)
@prompt = prompt
@prefix = options.fetch(:prefix) { @prompt.prefix }
@min = options.fetch(:min) { 0 }
@max = options.fetch(:max) { 10 }
@step = options.fetch(:step) { 1 }
@default = options[:default]
@active_color = options.fetch(:active_color) { @prompt.active_color }
@help_color = options.fetch(:help_color) { @prompt.help_color }
@format = options.fetch(:format) { FORMAT }
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
@first_render = true
@done = false
end
# Change symbols used by this prompt
#
# @param [Hash] new_symbols
# the new symbols to use
#
# @api public
def symbols(new_symbols = (not_set = true))
return @symbols if not_set
@symbols.merge!(new_symbols)
end
# Setup initial active position
#
# @return [Integer]
#
# @api private
def initial
if @default.nil?
range.size / 2
else
range.index(@default)
end
end
# Range of numbers to render
#
# @return [Array[Integer]]
#
# @apip private
def range
(@min..@max).step(@step).to_a
end
# @api public
def default(value)
@default = value
end
# @api public
def min(value)
@min = value
end
# @api public
def max(value)
@max = value
end
# @api public
def step(value)
@step = value
end
def format(value)
@format = value
end
# Call the slider by passing question
#
# @param [String] question
# the question to ask
#
# @apu public
def call(question, &block)
@question = question
block.call(self) if block
@active = initial
@prompt.subscribe(self) do
render
end
end
def keyleft(*)
@active -= 1 if @active > 0
end
alias keydown keyleft
def keyright(*)
@active += 1 if (@active + 1) < range.size
end
alias keyup keyright
def keyreturn(*)
@done = true
end
alias keyspace keyreturn
alias keyenter keyreturn
private
# Render an interactive range slider.
#
# @api private
def render
@prompt.print(@prompt.hide)
until @done
question = render_question
@prompt.print(question)
@prompt.read_keypress
refresh(question.lines.count)
end
@prompt.print(render_question)
answer
ensure
@prompt.print(@prompt.show)
end
# Clear screen
#
# @param [Integer] lines
# the lines to clear
#
# @api private
def refresh(lines)
@prompt.print(@prompt.clear_lines(lines))
end
# @return [Integer]
#
# @api private
def answer
range[@active]
end
# Render question with the slider
#
# @return [String]
#
# @api private
def render_question
header = ["#{@prefix}#{@question} "]
if @done
header << @prompt.decorate(answer.to_s, @active_color)
header << "\n"
else
header << render_slider
end
if @first_render
header << "\n" + @prompt.decorate(HELP, @help_color)
@first_render = false
end
header.join
end
# Render slider representation
#
# @return [String]
#
# @api private
def render_slider
slider = (@symbols[:line] * @active) +
@prompt.decorate(@symbols[:bullet], @active_color) +
(@symbols[:line] * (range.size - @active - 1))
value = " #{range[@active]}"
@format.gsub(':slider', slider) % [value]
end
end # Slider
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/statement.rb 0000664 0000000 0000000 00000002517 13631250432 0021175 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
# A class responsible for shell prompt interactions.
class Prompt
# A class representing a statement output to prompt.
class Statement
# Flag to display newline
#
# @api public
attr_reader :newline
# Color used to display statement
#
# @api public
attr_reader :color
# Initialize a Statement
#
# @param [TTY::Prompt] prompt
#
# @param [Hash] options
#
# @option options [Symbol] :newline
# force a newline break after the message
#
# @option options [Symbol] :color
# change the message display to color
#
# @api public
def initialize(prompt, options = {})
@prompt = prompt
@newline = options.fetch(:newline) { true }
@color = options.fetch(:color) { false }
end
# Output the message to the prompt
#
# @param [String] message
# the message to be printed to stdout
#
# @api public
def call(message)
message = @prompt.decorate(message, *color) if color
if newline && /( |\t)(\e\[\d+(;\d+)*m)?\Z/ !~ message
@prompt.puts message
else
@prompt.print message
@prompt.flush
end
end
end # Statement
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/suggestion.rb 0000664 0000000 0000000 00000005305 13631250432 0021356 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'distance'
module TTY
# A class responsible for terminal prompt interactions.
class Prompt
# A class representing a suggestion out of possible choices
#
# @api public
class Suggestion
DEFAULT_INDENT = 8
SINGLE_TEXT = 'Did you mean this?'
PLURAL_TEXT = 'Did you mean one of these?'
# Number of spaces
#
# @api public
attr_reader :indent
# Text for a single suggestion
#
# @api public
attr_reader :single_text
# Text for multiple suggestions
#
# @api public
attr_reader :plural_text
# Initialize a Suggestion
#
# @api public
def initialize(**options)
@indent = options.fetch(:indent) { DEFAULT_INDENT }
@single_text = options.fetch(:single_text) { SINGLE_TEXT }
@plural_text = options.fetch(:plural_text) { PLURAL_TEXT }
@suggestions = []
@comparator = Distance.new
end
# Suggest matches out of possibile strings
#
# @param [String] message
#
# @param [Array[String]] possibilities
#
# @api public
def suggest(message, possibilities)
distances = measure_distances(message, possibilities)
minimum_distance = distances.keys.min
max_distance = distances.keys.max
if minimum_distance < max_distance
@suggestions = distances[minimum_distance].sort
end
evaluate
end
private
# Measure distances between messag and possibilities
#
# @param [String] message
#
# @param [Array[String]] possibilities
#
# @return [Hash]
#
# @api private
def measure_distances(message, possibilities)
distances = Hash.new { |hash, key| hash[key] = [] }
possibilities.each do |possibility|
distances[@comparator.distance(message, possibility)] << possibility
end
distances
end
# Build up a suggestion string
#
# @param [Array[String]] suggestions
#
# @return [String]
#
# @api private
def evaluate
return @suggestions if @suggestions.empty?
if @suggestions.one?
build_single_suggestion
else
build_multiple_suggestions
end
end
# @api private
def build_single_suggestion
single_text + "\n" + (' ' * indent) + @suggestions.first
end
# @api private
def build_multiple_suggestions
plural_text + "\n" +
@suggestions.map do |sugest|
' ' * indent + sugest
end.join("\n")
end
end # Suggestion
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/symbols.rb 0000664 0000000 0000000 00000003667 13631250432 0020670 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
# Cross platform common Unicode symbols.
#
# @api public
module Symbols
KEYS = {
tick: '✓',
cross: '✘',
star: '★',
square: '◼',
square_empty: '◻',
dot: '•',
bullet: '●',
bullet_empty: '○',
marker: '‣',
line: '─',
pipe: '|',
ellipsis: '…',
radio_on: '⬢',
radio_off: '⬡',
checkbox_on: '☒',
checkbox_off: '☐',
circle: '◯',
circle_on: 'ⓧ',
circle_off: 'Ⓘ',
arrow_up: '↑',
arrow_down: '↓',
arrow_up_down: '↕',
arrow_left: '←',
arrow_right: '→',
arrow_left_right: '↔',
heart: '♥',
diamond: '♦',
club: '♣',
spade: '♠'
}.freeze
WIN_KEYS = {
tick: '√',
cross: 'x',
star: '*',
square: '[█]',
square_empty: '[ ]',
dot: '.',
bullet: 'O',
bullet_empty: '○',
marker: '>',
line: '-',
pipe: '|',
ellipsis: '...',
radio_on: '(*)',
radio_off: '( )',
checkbox_on: '[×]',
checkbox_off: '[ ]',
circle: '( )',
circle_on: '(x)',
circle_off: '( )',
arrow_up: '↑',
arrow_down: '↓',
arrow_up_down: '↕',
arrow_left: '←',
arrow_right: '→',
arrow_left_right: '↔',
heart: '♥',
diamond: '♦',
club: '♣',
spade: '♠'
}.freeze
def symbols
@symbols ||= windows? ? WIN_KEYS : KEYS
end
module_function :symbols
# Check if Windowz
#
# @return [Boolean]
#
# @api public
def windows?
::File::ALT_SEPARATOR == "\\"
end
module_function :windows?
end # Symbols
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/timer.rb 0000664 0000000 0000000 00000002620 13631250432 0020304 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
class Timer
attr_reader :duration
attr_reader :total
attr_reader :interval
def initialize(duration, interval)
@duration = duration
@interval = interval
@total = 0.0
@current = nil
@events = []
end
def start
return if @current
@current = time_now
end
def stop
return unless @current
@current = nil
end
def runtime
time_now - @current
end
def on_tick(&block)
@events << block
end
def while_remaining
start
remaining = duration
if @duration
while remaining >= 0.0
if runtime >= total
tick = duration - @total
@events.each { |block| block.(tick) }
@total += @interval
end
yield(remaining)
remaining = duration - runtime
end
else
loop { yield }
end
ensure
stop
end
if defined?(Process::CLOCK_MONOTONIC)
# Object representing current time
def time_now
::Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
else
# Object represeting current time
def time_now
::Time.now
end
end
end # Timer
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/utils.rb 0000664 0000000 0000000 00000001600 13631250432 0020321 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
module Utils
module_function
BLANK_REGEX = /\A[[:space:]]*\z/o.freeze
# Extract options hash from array argument
#
# @param [Array[Object]] args
#
# @api public
def extract_options(args)
options = args.last
options.respond_to?(:to_hash) ? options.to_hash.dup : {}
end
def extract_options!(args)
args.last.respond_to?(:to_hash) ? args.pop : {}
end
# Check if value is nil or an empty string
#
# @param [Object] value
# the value to check
#
# @return [Boolean]
#
# @api public
def blank?(value)
value.nil? ||
value.respond_to?(:empty?) && value.empty? ||
BLANK_REGEX === value
end
# Deep copy object
#
# @api public
def deep_copy(object)
Marshal.load(Marshal.dump(object))
end
end # Utils
end # TTY
tty-prompt-0.21.0/lib/tty/prompt/version.rb 0000664 0000000 0000000 00000000151 13631250432 0020646 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module TTY
class Prompt
VERSION = "0.21.0"
end # Prompt
end # TTY
tty-prompt-0.21.0/lib/tty/test_prompt.rb 0000664 0000000 0000000 00000000714 13631250432 0020225 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'prompt'
module TTY
# Used for initializing test cases
class TestPrompt < Prompt
def initialize(options = {})
@input = StringIO.new
@output = StringIO.new
options.merge!({
input: @input,
output: @output,
env: { "TTY_TEST" => true },
enable_color: options.fetch(:enable_color) { true }
})
super(options)
end
end # TestPrompt
end # TTY
tty-prompt-0.21.0/spec/ 0000775 0000000 0000000 00000000000 13631250432 0014662 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/spec_helper.rb 0000664 0000000 0000000 00000002510 13631250432 0017476 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
if ENV['COVERAGE'] || ENV['TRAVIS']
require 'simplecov'
require 'coveralls'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
])
SimpleCov.start do
command_name 'spec'
add_filter 'spec'
end
end
require 'tty-prompt'
require 'stringio'
class StringIO
def wait_readable(*)
true
end
end
module Helpers
def diff_output(actual_output, expected_output)
puts "ACTUAL: #{actual_output.inspect}"
puts "--------------------------------\n"
puts "EXPECT: #{expected_output.inspect}"
end
end
RSpec.configure do |config|
config.include(Helpers)
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
# Limits the available syntax to the non-monkey patched syntax that is recommended.
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 2
config.order = :random
Kernel.srand config.seed
end
tty-prompt-0.21.0/spec/unit/ 0000775 0000000 0000000 00000000000 13631250432 0015641 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/ask_spec.rb 0000664 0000000 0000000 00000011334 13631250432 0017760 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#ask' do
subject(:prompt) { TTY::TestPrompt.new }
it 'asks question' do
prompt.ask('What is your name?')
expect(prompt.output.string).to eq([
"What is your name? ",
"\e[1A\e[2K\e[1G",
"What is your name? \n"
].join)
end
it 'asks an empty question ' do
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
answer = prompt.ask
expect(answer).to eq(nil)
expect(prompt.output.string).to eql("\e[2K\e[1G\n\e[1A\e[2K\e[1G\n")
end
it "asks an empty question and returns nil if EOF is sent to stdin" do
prompt = TTY::TestPrompt.new
prompt.input << nil
prompt.input.rewind
answer = prompt.ask('')
expect(answer).to eql(nil)
expect(prompt.output.string).to eq("\e[1A\e[2K\e[1G\n")
end
it "asks an empty question with prepopulated value" do
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.ask value: "yes"
expect(answer).to eq("yes")
expect(prompt.output.string).to eq([
"yes\e[2K\e[1G",
"yes\n\e[1A\e[2K\e[1G",
"\e[32myes\e[0m\n"
].join)
end
it "asks question with prepopulated value" do
prompt = TTY::TestPrompt.new prefix: "> "
prompt.input << "\n"
prompt.input.rewind
answer = prompt.ask("Say?") do |q|
q.value "yes"
end
expect(answer).to eq("yes")
expect(prompt.output.string).to eq([
"> Say? yes\e[2K\e[1G",
"> Say? yes\n\e[1A\e[2K\e[1G",
"> Say? \e[32myes\e[0m\n"
].join)
end
it "asks a question with a prefix [?]" do
prompt = TTY::TestPrompt.new(prefix: "[?] ")
prompt.input << "\r"
prompt.input.rewind
answer = prompt.ask 'Are you Polish?'
expect(answer).to eq(nil)
expect(prompt.output.string).to eq([
"[?] Are you Polish? ",
"\e[2K\e[1G[?] Are you Polish? \n",
"\e[1A\e[2K\e[1G",
"[?] Are you Polish? \n"
].join)
end
it 'asks a question with block' do
prompt.input << ''
prompt.input.rewind
answer = prompt.ask "What is your name?" do |q|
q.default 'Piotr'
end
expect(answer).to eq('Piotr')
expect(prompt.output.string).to eq([
"What is your name? \e[90m(Piotr)\e[0m ",
"\e[1A\e[2K\e[1G",
"What is your name? \e[32mPiotr\e[0m\n"
].join)
end
it "changes question color" do
prompt.input << ''
prompt.input.rewind
options = {default: 'Piotr', help_color: :red, active_color: :cyan}
answer = prompt.ask("What is your name?", **options)
expect(answer).to eq('Piotr')
expect(prompt.output.string).to eq([
"What is your name? \e[31m(Piotr)\e[0m ",
"\e[1A\e[2K\e[1G",
"What is your name? \e[36mPiotr\e[0m\n"
].join)
end
it "permits empty default parameter" do
prompt.input << "\r"
prompt.input.rewind
answer = prompt.ask("What is your name?", default: '')
expect(answer).to eq('')
expect(prompt.output.string).to eq([
"What is your name? ",
"\e[2K\e[1GWhat is your name? \n",
"\e[1A\e[2K\e[1G",
"What is your name? \n"
].join)
end
it "permits nil default parameter" do
prompt.input << "\r"
prompt.input.rewind
answer = prompt.ask("What is your name?", default: nil)
expect(answer).to eq(nil)
expect(prompt.output.string).to eq([
"What is your name? ",
"\e[2K\e[1GWhat is your name? \n",
"\e[1A\e[2K\e[1G",
"What is your name? \n"
].join)
end
it "overwrites global settings" do
global_settings = {prefix: "[?] ", active_color: :cyan, help_color: :red}
prompt = TTY::TestPrompt.new(global_settings)
prompt.input << "Piotr\r"
prompt.input.rewind
prompt.ask('What is your name?')
prompt.input << "Piotr\r"
prompt.input.rewind
local_settings = {prefix: ':-) ', active_color: :blue, help_color: :magenta}
prompt.ask('What is your name?', **local_settings)
expect(prompt.output.string).to eq([
"[?] What is your name? ",
"\e[2K\e[1G[?] What is your name? P",
"\e[2K\e[1G[?] What is your name? Pi",
"\e[2K\e[1G[?] What is your name? Pio",
"\e[2K\e[1G[?] What is your name? Piot",
"\e[2K\e[1G[?] What is your name? Piotr",
"\e[2K\e[1G[?] What is your name? Piotr\n",
"\e[1A\e[2K\e[1G",
"[?] What is your name? \e[36mPiotr\e[0m\n",
":-) What is your name? ",
"\e[2K\e[1G:-) What is your name? P",
"\e[2K\e[1G:-) What is your name? Pi",
"\e[2K\e[1G:-) What is your name? Pio",
"\e[2K\e[1G:-) What is your name? Piot",
"\e[2K\e[1G:-) What is your name? Piotr",
"\e[2K\e[1G:-) What is your name? Piotr\n",
"\e[1A\e[2K\e[1G",
":-) What is your name? \e[34mPiotr\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/block_paginator_spec.rb 0000664 0000000 0000000 00000005771 13631250432 0022350 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::BlockPaginator, '#paginate' do
it "ignores per_page when equal items " do
list = %w(a b c d)
paginator = described_class.new(per_page: 4)
expect(paginator.paginate(list, 1).to_a).to eq([
['a',0],['b',1],['c',2],['d',3]])
end
it "ignores per_page when less items " do
list = %w(a b c d)
paginator = described_class.new(per_page: 5)
expect(paginator.paginate(list, 1).to_a).to eq([
['a',0],['b',1],['c',2],['d',3]])
end
it "paginates items matching per_page count" do
list = %w(a b c d e f)
paginator = described_class.new(per_page: 3)
expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 7).to_a).to eq([['d',3], ['e',4], ['f',5]])
end
it "paginates items not matching per_page count" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3)
expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 7).to_a).to eq([['g',6]])
expect(paginator.paginate(list, 8).to_a).to eq([['g',6]])
end
it "finds both start and end index for current selection" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3, default: 0)
paginator.paginate(list, 3)
expect(paginator.start_index).to eq(0)
expect(paginator.end_index).to eq(2)
paginator.paginate(list, 4)
expect(paginator.start_index).to eq(3)
expect(paginator.end_index).to eq(5)
paginator.paginate(list, 5)
expect(paginator.start_index).to eq(3)
expect(paginator.end_index).to eq(5)
paginator.paginate(list, 7)
expect(paginator.start_index).to eq(6)
expect(paginator.end_index).to eq(6)
end
it "starts with default selection" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3, default: 3)
expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
end
it "doesn't accept invalid pagination" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 0)
expect {
paginator.paginate(list, 4)
}.to raise_error(TTY::Prompt::InvalidArgument, /per_page must be > 0/)
end
end
tty-prompt-0.21.0/spec/unit/choice/ 0000775 0000000 0000000 00000000000 13631250432 0017073 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/choice/eql_spec.rb 0000664 0000000 0000000 00000001200 13631250432 0021204 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choice, '#==' do
it "is true with the same name and value attributes" do
expect(described_class.new(:large, 1)).
to eq(described_class.new(:large, 1))
end
it "is false with different name attribute" do
expect(described_class.new(:large, 1)).
not_to eq(described_class.new(:medium, 1))
end
it "is false with different value attribute" do
expect(described_class.new(:large, 1)).
not_to eq(described_class.new(:large, 2))
end
it "is false with non-choice object" do
expect(described_class.new(:large, 1)).not_to eq(:other)
end
end
tty-prompt-0.21.0/spec/unit/choice/from_spec.rb 0000664 0000000 0000000 00000007645 13631250432 0021411 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choice, '#from' do
it "skips Choice instance" do
choice = described_class.new(:large, 1)
expect(described_class.from(choice)).to eq(choice)
end
it "creates choice from string" do
expected_choice = described_class.new('large', 'large')
choice = described_class.from('large')
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq('large')
end
it "creates choice from array with one element" do
expected_choice = described_class.new('large', 'large')
choice = described_class.from([:large])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq('large')
end
it "creates choice from array with more than one element" do
expected_choice = described_class.new('large', 1)
choice = described_class.from([:large, 1])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq(1)
end
it "creates choice from array with false" do
expected_choice = described_class.new('large', false)
choice = described_class.from([:large, false])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq(false)
end
it "defaults value to name if value is nil" do
expected_choice = described_class.new('large', 'large')
choice = described_class.from([:large, nil])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq('large')
end
it "creates choice from hash value" do
expected_choice = described_class.new('large', 1)
choice = described_class.from({large: 1})
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq(1)
end
it "creats choice from array with key value pair" do
expected_choice = described_class.new('large', 1)
choice = described_class.from([{'large' => 1}])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq(1)
end
it "creats choice from array with hash elements" do
expected_choice = described_class.new('large', 1)
choice = described_class.from([{name: 'large', value: 1}])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq(1)
end
it "creats choice from array with hash elements without value" do
expected_choice = described_class.new('large', 'large')
choice = described_class.from([{name: 'large'}])
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('large')
expect(choice.value).to eq('large')
end
it "creates choice from hash with key property" do
default = {key: 'h', name: 'Help', value: :help}
expected_choice = described_class.new('Help', :help, key: 'h')
choice = described_class.from(default)
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('Help')
expect(choice.value).to eq(:help)
expect(choice.disabled?).to eq(false)
end
it "creates disabled choice" do
expected_choice = described_class.new('Disabled', :none, disabled: true)
choice = described_class.from({
name: 'Disabled',
value: :none,
disabled: 'unavailable'})
expect(choice).to eq(expected_choice)
expect(choice.name).to eq('Disabled')
expect(choice.value).to eq(:none)
expect(choice.disabled?).to eq(true)
end
it "creates choice from an arbitrary object that responds to to_s call" do
stub_const("Size", Class.new do
def to_s
'large'
end
end)
size = Size.new
expected_choice = described_class.new(size, size)
choice = described_class.from(size)
expect(choice).to eq(expected_choice)
expect(choice.name).to eq(size)
expect(choice.value).to eq(size)
expect(choice.disabled?).to eq(false)
end
end
tty-prompt-0.21.0/spec/unit/choices/ 0000775 0000000 0000000 00000000000 13631250432 0017256 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/choices/add_spec.rb 0000664 0000000 0000000 00000000530 13631250432 0021343 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choices, '#<<' do
it "adds choice to collection" do
choices = described_class.new
expect(choices).to be_empty
choice = TTY::Prompt::Choice.from([:label, 1])
choices << [:label, 1]
expect(choices.size).to eq(1)
expect(choices.to_ary).to eq([choice])
end
end
tty-prompt-0.21.0/spec/unit/choices/each_spec.rb 0000664 0000000 0000000 00000000553 13631250432 0021520 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choices, '.each' do
it "iterates over collection" do
choices = described_class[:large, :medium, :small]
actual = []
choices.each do |choice|
actual << choice.name
end
expect(actual).to eq([:large, :medium, :small])
expect(choices.each).to be_kind_of(Enumerator)
end
end
tty-prompt-0.21.0/spec/unit/choices/find_by_spec.rb 0000664 0000000 0000000 00000000550 13631250432 0022227 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choices, '#find_by' do
it "finds a matching choice by key name" do
collection = [{name: 'large'},{name: 'medium'},{name: 'small'}]
choice = TTY::Prompt::Choice.from(name: 'small')
choices = described_class[*collection]
expect(choices.find_by(:name, 'small')).to eq(choice)
end
end
tty-prompt-0.21.0/spec/unit/choices/new_spec.rb 0000664 0000000 0000000 00000000515 13631250432 0021407 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choices, '.new' do
it "creates choices collection" do
choice_1 = TTY::Prompt::Choice.from(:label1)
choice_2 = TTY::Prompt::Choice.from(:label2)
collection = described_class[:label1, :label2]
expect(collection.choices).to eq([choice_1, choice_2])
end
end
tty-prompt-0.21.0/spec/unit/choices/pluck_spec.rb 0000664 0000000 0000000 00000000462 13631250432 0021735 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Choices, '#pluck' do
it "plucks choice by key name" do
collection = [{name: 'large'},{name: 'medium'},{name: 'small'}]
choices = described_class[*collection]
expect(choices.pluck(:name)).to eq(['large', 'medium', 'small'])
end
end
tty-prompt-0.21.0/spec/unit/collect_spec.rb 0000664 0000000 0000000 00000004474 13631250432 0020636 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#collect' do
subject(:prompt) { TTY::TestPrompt.new }
def collect(&block)
prompt = subject
count = 0
result = prompt.collect do
while prompt.yes?("continue?")
instance_eval(&block)
count += 1
end
end
result[:count] = count
result
end
context "when receiving multiple answers" do
let(:colors) { %w(red blue yellow) }
before do
subject.input << "y\r" + colors.join("\ry\r") + "\rn\r"
subject.input.rewind
end
it "collects as a list if values method used in chain" do
result = collect { key(:colors).values.ask("color:") }
expect(result[:count]).to eq(3)
expect(result[:colors]).to eq(colors)
end
it "collects as a list if values method used in chain with block" do
result = collect do
key(:colors).values { key(:name).ask("color:") }
end
expect(result[:count]).to eq(3)
expect(result[:colors]).to eq(colors.map { |c| { name: c } })
end
context "with multiple keys" do
let(:colors) { ["red\rblue", "yellow\rgreen"] }
let(:expected_pairs) do
colors.map { |s| Hash[%i(hot cold).zip(s.split("\r"))] }
end
it "collects into the appropriate keys" do
result = collect do
key(:pairs).values do
key(:hot).ask("color:")
key(:cold).ask("color:")
end
end
expect(result[:count]).to eq(2)
expect(result[:pairs]).to eq(expected_pairs)
end
end
it "overrides a non-array key on multiple answers" do
result = collect { key(:colors).ask("color:") }
expect(result[:colors]).to eq(colors.last)
expect(result[:count]).to eq(3)
end
end
it "collects more than one answer" do
prompt.input << "Piotr\r30\rStreet\rCity\r123\r"
prompt.input.rewind
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
expect(result).to include({
name: 'Piotr',
age: 30,
address: {
street: 'Street',
city: 'City',
zip: '123'
}
})
end
end
tty-prompt-0.21.0/spec/unit/converters/ 0000775 0000000 0000000 00000000000 13631250432 0020033 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/converters/convert_bool_spec.rb 0000664 0000000 0000000 00000003054 13631250432 0024067 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert bool' do
subject(:prompt) { TTY::TestPrompt.new}
it 'fails to convert boolean' do
prompt.input << 'invalid'
prompt.input.rewind
expect {
prompt.ask("Do you read books?", convert: :bool)
}.to raise_error(TTY::Prompt::ConversionError)
end
it "handles default values" do
prompt.input << "\n"
prompt.input.rewind
response = prompt.ask('Do you read books?', convert: :bool, default: true)
expect(response).to eql(true)
expect(prompt.output.string).to eq([
"Do you read books? \e[90m(true)\e[0m ",
"\e[2K\e[1GDo you read books? \e[90m(true)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Do you read books? \e[32mtrue\e[0m\n"
].join)
end
it "handles default values" do
prompt.input << "\n"
prompt.input.rewind
response = prompt.ask("Do you read books?") { |q|
q.default true
q.convert :bool
}
expect(response).to eq(true)
end
it 'converts negative boolean' do
prompt.input << 'No'
prompt.input.rewind
response = prompt.ask('Do you read books?', convert: :bool)
expect(response).to eq(false)
end
it 'converts positive boolean' do
prompt.input << 'Yes'
prompt.input.rewind
response = prompt.ask("Do you read books?", convert: :bool)
expect(response).to eq(true)
end
it 'converts single positive boolean' do
prompt.input << 'y'
prompt.input.rewind
response = prompt.ask('Do you read books?', convert: :bool)
expect(response).to eq(true)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_char_spec.rb 0000664 0000000 0000000 00000000503 13631250432 0024045 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert char' do
it 'reads single character' do
prompt = TTY::TestPrompt.new
prompt.input << "abcde"
prompt.input.rewind
response = prompt.ask("What is your favourite letter?", convert: :char)
expect(response).to eq('a')
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_custom_spec.rb 0000664 0000000 0000000 00000000665 13631250432 0024453 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert custom' do
subject(:prompt) { TTY::TestPrompt.new }
it 'converts response with custom conversion' do
prompt.input << "one,two,three\n"
prompt.input.rewind
conversion = proc { |input| input.split(/,\s*/) }
answer = prompt.ask('Ingredients? (comma sep list)', convert: conversion)
expect(answer).to eq(['one','two','three'])
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_date_spec.rb 0000664 0000000 0000000 00000001721 13631250432 0024050 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert date' do
subject(:prompt) { TTY::TestPrompt.new}
it 'fails to convert date' do
prompt.input << 'invalid'
prompt.input.rewind
expect {
prompt.ask("When were you born?", convert: :date)
}.to raise_error(TTY::Prompt::ConversionError)
end
it 'converts date' do
prompt.input << "20th April 1887"
prompt.input.rewind
response = prompt.ask("When were your born?", convert: :date)
expect(response).to be_kind_of(Date)
expect(response.day).to eq(20)
expect(response.month).to eq(4)
expect(response.year).to eq(1887)
end
it "converts datetime" do
prompt.input << "20th April 1887"
prompt.input.rewind
response = prompt.ask("When were your born?", convert: :datetime)
expect(response).to be_kind_of(DateTime)
expect(response.day).to eq(20)
expect(response.month).to eq(4)
expect(response.year).to eq(1887)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_file_spec.rb 0000664 0000000 0000000 00000000733 13631250432 0024054 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert file' do
it "converts to file" do
::File.write('test.txt', 'foobar')
prompt = TTY::TestPrompt.new
prompt.input << "test.txt"
prompt.input.rewind
answer = prompt.ask("Which file to open?", convert: :file)
expect(::File.basename(answer)).to eq('test.txt')
expect(::File.read(answer)).to eq('foobar')
::File.unlink('test.txt') unless Gem.win_platform?
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_number_spec.rb 0000664 0000000 0000000 00000001750 13631250432 0024425 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert numbers' do
subject(:prompt) { TTY::TestPrompt.new }
it 'fails to convert integer' do
prompt.input << 'invalid'
prompt.input.rewind
expect {
prompt.ask("What temparture?", convert: :int)
}.to raise_error(TTY::Prompt::ConversionError)
end
it 'converts integer' do
prompt.input << 35
prompt.input.rewind
answer = prompt.ask("What temperature?", convert: :int)
expect(answer).to be_a(Integer)
expect(answer).to eq(35)
end
it 'fails to convert float' do
prompt.input << 'invalid'
prompt.input.rewind
expect {
prompt.ask("How tall are you?", convert: :float)
}.to raise_error(TTY::Prompt::ConversionError)
end
it 'converts float' do
number = 6.666
prompt.input << number
prompt.input.rewind
answer = prompt.ask('How tall are you?', convert: :float)
expect(answer).to be_a(Float)
expect(answer).to eq(number)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_path_spec.rb 0000664 0000000 0000000 00000000570 13631250432 0024070 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert path' do
subject(:prompt) { TTY::TestPrompt.new }
it "converts pathname" do
path = Pathname.new(::File.join(Dir.pwd, 'spec/unit'))
prompt.input << "spec/unit"
prompt.input.rewind
answer = prompt.ask('File location?', convert: :path)
expect(answer).to eql(path)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_range_spec.rb 0000664 0000000 0000000 00000001072 13631250432 0024226 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert range' do
subject(:prompt) { TTY::TestPrompt.new}
it 'converts with valid range' do
prompt.input << "20-30"
prompt.input.rewind
answer = prompt.ask("Which age group?", convert: :range)
expect(answer).to be_a(Range)
expect(answer).to eq(20..30)
end
it "fails to convert to range" do
prompt.input << "abcd"
prompt.input.rewind
expect {
prompt.ask('Which age group?', convert: :range)
}.to raise_error(TTY::Prompt::ConversionError)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_regex_spec.rb 0000664 0000000 0000000 00000000514 13631250432 0024244 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert regexp' do
it "converts regex" do
prompt = TTY::TestPrompt.new
prompt.input << "[a-z]*"
prompt.input.rewind
answer = prompt.ask("Regex?", convert: :regexp)
expect(answer).to be_a(Regexp)
expect(answer).to eq(/[a-z]*/)
end
end
tty-prompt-0.21.0/spec/unit/converters/convert_string_spec.rb 0000664 0000000 0000000 00000001121 13631250432 0024433 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, 'convert string' do
it 'converts string' do
prompt = TTY::TestPrompt.new
prompt.input << 'Piotr'
prompt.input.rewind
answer = prompt.ask("What is your name?", convert: :string)
expect(answer).to be_a(String)
expect(answer).to eq('Piotr')
end
it "converts symbol" do
prompt = TTY::TestPrompt.new
prompt.input << 'Piotr'
prompt.input.rewind
answer = prompt.ask("What is your name?", convert: :symbol)
expect(answer).to be_a(Symbol)
expect(answer).to eq(:Piotr)
end
end
tty-prompt-0.21.0/spec/unit/converters/on_error_spec.rb 0000664 0000000 0000000 00000000366 13631250432 0023224 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Converters do
it "enforces block argument" do
expect {
TTY::Prompt::Converters.on_error
}.to raise_error(ArgumentError, 'You need to provide a block argument.')
end
end
tty-prompt-0.21.0/spec/unit/distance/ 0000775 0000000 0000000 00000000000 13631250432 0017433 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/distance/distance_spec.rb 0000664 0000000 0000000 00000002606 13631250432 0022570 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Distance, '.distance' do
let(:object) { described_class.new }
subject(:distance) { object.distance(*strings) }
context 'when nil' do
let(:strings) { [nil, nil] }
it { is_expected.to eql(0) }
end
context 'when empty' do
let(:strings) { ['', ''] }
it { is_expected.to eql(0) }
end
context 'with one non empty' do
let(:strings) { ['abc', ''] }
it { is_expected.to eql(3) }
end
context 'when single char' do
let(:strings) { ['a', 'abc'] }
it { is_expected.to eql(2) }
end
context 'when similar' do
let(:strings) { ['abc', 'abc'] }
it { is_expected.to eql(0) }
end
context 'when similar' do
let(:strings) { ['abc', 'acb'] }
it { is_expected.to eql(1) }
end
context 'when end similar' do
let(:strings) { ['saturday', 'sunday'] }
it { is_expected.to eql(3) }
end
context 'when contain similar' do
let(:strings) { ['which', 'witch'] }
it { is_expected.to eql(2) }
end
context 'when prefix' do
let(:strings) { ['sta', 'status'] }
it { is_expected.to eql(3) }
end
context 'when similar' do
let(:strings) { ['smellyfish','jellyfish'] }
it { is_expected.to eql(2) }
end
context 'when unicode' do
let(:strings) { ['マラソン五輪代表', 'ララソン五輪代表'] }
it { is_expected.to eql(1) }
end
end
tty-prompt-0.21.0/spec/unit/enum_select_spec.rb 0000664 0000000 0000000 00000037406 13631250432 0021515 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt do
let(:symbols) { TTY::Prompt::Symbols.symbols }
def output_helper(prompt, choices, active, options = {})
enum = options.fetch(:enum, ')')
input = options[:input]
error = options[:error]
default = options.fetch(:default, 1)
out = []
out << prompt << " \n"
out << choices.map.with_index do |c, i|
name = c.is_a?(Hash) ? c[:name] : c
disabled = c.is_a?(Hash) ? c[:disabled] : false
num = (i + 1).to_s + enum
if disabled
"\e[31m#{symbols[:cross]}\e[0m #{num} #{name} #{disabled}"
elsif name == active
" \e[32m#{num} #{name}\e[0m"
else
" #{num} #{name}"
end
end.join("\n")
out << "\n"
choice = " Choose 1-#{choices.count} [#{default}]: "
choice = choice + input.to_s if input
out << choice
if error
out << "\n"
out << "\e[31m>>\e[0m #{error}"
out << "\e[A\e[1G\e[#{choice.size}C"
end
out << "\e[2K\e[1G\e[1A" * (choices.count + 1)
out << "\e[2K\e[1G\e[J"
out.join
end
def exit_message(prompt, choice)
"#{prompt} \e[32m#{choice}\e[0m\n"
end
it "raises configuration error when wrong default" do
prompt = TTY::TestPrompt.new
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
expect {
prompt.enum_select("Select an editor?", choices, default: 100)
}.to raise_error(TTY::Prompt::ConfigurationError,
/default index 100 out of range \(1 - 3\)/)
end
it "selects default option when return pressed immediately" do
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("Select an editor?", choices)
expect(answer).to eq('/bin/nano')
expected_output = [
output_helper("Select an editor?", choices, "/bin/nano"),
exit_message("Select an editor?", "/bin/nano")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "selects option by index from the list" do
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt = TTY::TestPrompt.new
prompt.input << "3\n"
prompt.input.rewind
answer = prompt.enum_select("Select an editor?", choices, default: 2)
expect(answer).to eq('/usr/bin/vim.tiny')
expected_output = [
output_helper("Select an editor?", choices, "/usr/bin/vim.basic", default: 2),
output_helper("Select an editor?", choices, "/usr/bin/vim.tiny", default: 2, input: '3'),
exit_message("Select an editor?", "/usr/bin/vim.tiny")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "selects option through DSL" do
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt = TTY::TestPrompt.new
prompt.input << "1\n"
prompt.input.rewind
answer = prompt.enum_select("Select an editor?") do |menu|
menu.default 2
menu.enum '.'
menu.choice "/bin/nano"
menu.choice "/usr/bin/vim.basic"
menu.choice "/usr/bin/vim.tiny"
end
expect(answer).to eq('/bin/nano')
expected_output = [
output_helper("Select an editor?", choices, "/usr/bin/vim.basic", default: 2, enum: '.'),
output_helper("Select an editor?", choices, "/bin/nano", default: 2, enum: '.', input: 1),
exit_message("Select an editor?", "/bin/nano")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "selects option through DSL with key and value" do
choices = %w(nano vim emacs)
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("Select an editor?") do |menu|
menu.default 2
menu.choice :nano, '/bin/nano'
menu.choice :vim, '/usr/bin/vim'
menu.choice :emacs, '/usr/bin/emacs'
end
expect(answer).to eq('/usr/bin/vim')
expected_output = [
output_helper("Select an editor?", choices, "vim", default: 2),
exit_message("Select an editor?", "vim")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "changes colors for selection, hint and error" do
prompt = TTY::TestPrompt.new
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt.input << "\n"
prompt.input.rewind
options = {active_color: :red, help_color: :blue, error_color: :green}
answer = prompt.enum_select("Select an editor?", choices, options)
expect(answer).to eq('/bin/nano')
expected_output = [
"Select an editor? \n",
" \e[31m1) /bin/nano\e[0m\n",
" 2) /usr/bin/vim.basic\n",
" 3) /usr/bin/vim.tiny\n",
" Choose 1-3 [1]: ",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"Select an editor? \e[31m/bin/nano\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "changes global symbols" do
prompt = TTY::TestPrompt.new(symbols: {cross: 'x'})
choices = ['A', {name: 'B', disabled: '(out)'}, 'C']
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices)
expect(answer).to eq("A")
expected_output = [
"What letter? \n",
" \e[32m1) A\e[0m\n",
"\e[31mx\e[0m 2) B (out)\n",
" 3) C\n",
" Choose 1-3 [1]: ",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mA\e[0m\n",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "changes global symbols through DSL" do
prompt = TTY::TestPrompt.new
choices = ['A', {name: 'B', disabled: '(out)'}, 'C']
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices) do |menu|
menu.symbols cross: 'x'
menu.choices choices
end
expect(answer).to eq("A")
expected_output = [
"What letter? \n",
" \e[32m1) A\e[0m\n",
"\e[31mx\e[0m 2) B (out)\n",
" 3) C\n",
" Choose 1-3 [1]: ",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mA\e[0m\n",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "displays error with unrecognized input" do
choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
prompt = TTY::TestPrompt.new
prompt.input << "11\n2\n"
prompt.input.rewind
answer = prompt.enum_select("Select an editor?", choices)
expect(answer).to eq('/usr/bin/vim.basic')
expected_output = [
output_helper("Select an editor?", choices, "/bin/nano"),
output_helper("Select an editor?", choices, "/bin/nano", input: '1'),
output_helper("Select an editor?", choices, "/bin/nano", input: '11'),
output_helper("Select an editor?", choices, "/bin/nano", error: 'Please enter a valid number', input: ''),
output_helper("Select an editor?", choices, "/usr/bin/vim.basic", error: 'Please enter a valid number', input: '2'),
exit_message("Select an editor?", "/usr/bin/vim.basic")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "paginates long selections" do
choices = %w(A B C D E F G H)
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices, per_page: 3, default: 4)
expect(answer).to eq('D')
expect(prompt.output.string).to eq([
"What letter? \n",
" \e[32m4) D\e[0m\n",
" 5) E\n",
" 6) F\n",
" Choose 1-8 [4]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mD\e[0m\n"
].join)
end
it "doesn't paginate short selections" do
choices = %i(A B C D)
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices, per_page: 4, default: 1)
expect(answer).to eq(:A)
expected_output =
output_helper("What letter?", choices, :A) +
exit_message("What letter?", :A)
expect(prompt.output.string).to eq(expected_output)
end
it "shows pages matching input" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F G H)
prompt.input << "11\n\b\n"
prompt.input.rewind
value = prompt.enum_select("What letter?", choices, per_page: 3)
expect(value).to eq('A')
expect(prompt.output.string).to eq([
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-8 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-8 [1]: 1",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[19C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-8 [1]: 11",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[20C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-8 [1]: \n",
"\e[31m>>\e[0m Please enter a valid number",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-8 [1]: \n",
"\e[31m>>\e[0m Please enter a valid number",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mA\e[0m\n"
].join)
end
it "switches through pages with tab key" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F G H)
prompt.input << "\t\n"
prompt.input.rewind
value = prompt.enum_select("What letter?") do |menu|
menu.default 4
menu.per_page 3
menu.choices choices
end
expect(value).to eq('D')
expect(prompt.output.string).to eq([
"What letter? \n",
" \e[32m4) D\e[0m\n",
" 5) E\n",
" 6) F\n",
" Choose 1-8 [4]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" 7) G\n",
" 8) H\n",
" Choose 1-8 [4]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G\e[J",
"What letter? \e[32mD\e[0m\n"
].join)
end
it "doesn't cycle around by default" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F)
prompt.input << "\t" << "\t" << "\n"
prompt.input.rewind
value = prompt.enum_select("What letter?") do |menu|
menu.default 1
menu.per_page 3
menu.choices choices
end
expect(value).to eq("A")
expect(prompt.output.string).to eq([
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" 4) D\n",
" 5) E\n",
" 6) F\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" 4) D\n",
" 5) E\n",
" 6) F\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mA\e[0m\n"
].join)
end
it "cycles around when configured to do so" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F)
prompt.input << "\t" << "\t" << "\n"
prompt.input.rewind
value = prompt.enum_select("What letter?", cycle: true) do |menu|
menu.default 1
menu.per_page 3
menu.choices choices
end
expect(value).to eq("A")
expect(prompt.output.string).to eq([
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" 4) D\n",
" 5) E\n",
" 6) F\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \n",
" \e[32m1) A\e[0m\n",
" 2) B\n",
" 3) C\n",
" Choose 1-6 [1]: ",
"\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
"\e[A\e[1G\e[18C",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G\e[J",
"What letter? \e[32mA\e[0m\n"
].join)
end
context "with :disabled choice" do
it "fails when active item is also disabled" do
prompt = TTY::TestPrompt.new
choices = [{name: 'A', disabled: true}, 'B', 'C', 'D', 'E']
expect {
prompt.enum_select("What letter?", choices, default: 1)
}.to raise_error(TTY::Prompt::ConfigurationError,
/default index 1 matches disabled choice item/)
end
it "finds first non-disabled index" do
prompt = TTY::TestPrompt.new
choices = [{name: 'A', disabled: true}, {name:'B', disabled: true}, 'C', 'D']
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices)
expect(answer).to eq('C')
end
it "doesn't allow to choose disabled choice and defaults" do
choices = ['A', {name: 'B', disabled: '(out)'}, 'C', 'D', 'E', 'F']
prompt = TTY::TestPrompt.new
prompt.input << "2" << "\n" << "3" << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices)
expect(answer).to eq("C")
expected_output = [
output_helper("What letter?", choices, 'A'),
output_helper("What letter?", choices, 'A', input: '2'),
output_helper("What letter?", choices, 'A', input: '', error: 'Please enter a valid number'),
output_helper("What letter?", choices, 'C', input: '3', error: 'Please enter a valid number'),
exit_message("What letter?", "C")
].join
expect(prompt.output.string).to eq(expected_output)
end
it "omits disabled choice when navigating with numbers" do
choices = [
{name: 'A'},
{name: 'B', disabled: '(out)'},
{name: 'C', disabled: '(out)'},
{name: 'D'},
{name: 'E'}
]
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keydelete) if e.value == "B"}
prompt.input << "2" << "\u007F" << "3" << "\u007F" << '4' << "\n"
prompt.input.rewind
answer = prompt.enum_select("What letter?", choices)
expect(answer).to eq("D")
expected_output = [
output_helper("What letter?", choices, 'A'),
output_helper("What letter?", choices, 'A', input: '2'),
output_helper("What letter?", choices, 'A', input: ''),
output_helper("What letter?", choices, 'A', input: '3'),
output_helper("What letter?", choices, 'A', input: ''),
output_helper("What letter?", choices, 'D', input: '4'),
exit_message("What letter?", "D")
].join
expect(prompt.output.string).to eq(expected_output)
end
end
end
tty-prompt-0.21.0/spec/unit/error_spec.rb 0000664 0000000 0000000 00000001161 13631250432 0020330 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '.error' do
subject(:prompt) { TTY::TestPrompt.new }
it 'displays one message' do
prompt.error "Nothing is fine!"
expect(prompt.output.string).to eql "\e[31mNothing is fine!\e[0m\n"
end
it 'displays many messages' do
prompt.error "Nothing is fine!", "All is broken!"
expect(prompt.output.string).to eql "\e[31mNothing is fine!\e[0m\n\e[31mAll is broken!\e[0m\n"
end
it 'displays message with option' do
prompt.error "Nothing is fine!", newline: false
expect(prompt.output.string).to eql "\e[31mNothing is fine!\e[0m"
end
end
tty-prompt-0.21.0/spec/unit/evaluator_spec.rb 0000664 0000000 0000000 00000003034 13631250432 0021202 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Evaluator do
it "checks chained validation procs" do
question = double(:question)
evaluator = TTY::Prompt::Evaluator.new(question)
evaluator.check { |quest, value|
if value < 21
[value, ["#{value} is not bigger than 21"]]
else
value
end
}
evaluator.check { |quest, value|
if value < 42
[value, ["#{value} is not bigger than 42"]]
else
value
end
}
answer = evaluator.call(2)
expect(answer.errors.count).to eq(2)
expect(answer.value).to eq(2)
expect(answer.success?).to eq(false)
expect(answer.failure?).to eq(true)
end
it "checks chained validation objects" do
question = double(:question)
evaluator = TTY::Prompt::Evaluator.new(question)
LessThan21 = Class.new do
def self.call(quest, value)
if value < 21
[value, ["#{value} is not bigger than 21"]]
else
value
end
end
end
LessThan42 = Class.new do
def self.call(quest, value)
if value < 42
[value, ["#{value} is not bigger than 42"]]
else
value
end
end
end
evaluator.check(LessThan21)
evaluator.check(LessThan42)
answer = evaluator.call(2)
expect(answer.errors).to match_array([
"2 is not bigger than 21",
"2 is not bigger than 42"
])
expect(answer.value).to eq(2)
expect(answer.success?).to eq(false)
expect(answer.failure?).to eq(true)
end
end
tty-prompt-0.21.0/spec/unit/expand_spec.rb 0000664 0000000 0000000 00000017757 13631250432 0020500 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#expand' do
subject(:prompt) { TTY::TestPrompt.new }
let(:choices) {
[{
key: 'y',
name: 'Overwrite',
value: :yes
}, {
key: 'n',
name: 'Skip',
value: :no
}, {
key: 'a',
name: 'Overwrite all',
value: :all
}, {
key: 'd',
name: 'Show diff',
value: :diff
}, {
key: 'q',
name: 'Quit',
value: :quit
}]
}
it "expands default option" do
prompt.input << "\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices)
expect(result).to eq(:yes)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? \e[32mOverwrite\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "changes default option" do
prompt.input << "\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices, default: 3)
expect(result).to eq(:all)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [y,n,\e[32ma\e[0m,d,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? \e[32mOverwrite all\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "expands chosen option with extra information" do
prompt.input << "a\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices)
expect(result).to eq(:all)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [y,n,\e[32ma\e[0m,d,q,h] ",
"a\n",
"\e[32m>> \e[0mOverwrite all",
"\e[A\e[1G\e[55C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? \e[32mOverwrite all\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "expands help option and then defaults" do
prompt.input << "h\nd\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices)
expect(result).to eq(:diff)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [y,n,a,d,q,\e[32mh\e[0m] h\n",
"\e[32m>> \e[0mprint help",
"\e[A\e[1G\e[55C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? \n",
" y - Overwrite\n",
" n - Skip\n",
" a - Overwrite all\n",
" d - Show diff\n",
" q - Quit\n",
" h - print help\n",
" Choice [y]: ",
"\e[2K\e[1G\e[1A" * 7,
"\e[2K\e[1G",
"Overwrite Gemfile? \n",
" y - Overwrite\n",
" n - Skip\n",
" a - Overwrite all\n",
" \e[32md - Show diff\e[0m\n",
" q - Quit\n",
" h - print help\n",
" Choice [y]: d",
"\e[2K\e[1G\e[1A" * 7,
"\e[2K\e[1G",
"Overwrite Gemfile? \e[32mShow diff\e[0m\n",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "automatically expands hint" do
prompt.input << "d\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices, auto_hint: true)
expect(result).to eq(:diff)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\n\e[32m>> \e[0mOverwrite",
"\e[A\e[1G\e[54C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [y,n,a,\e[32md\e[0m,q,h] ",
"d\n",
"\e[32m>> \e[0mShow diff",
"\e[A\e[1G\e[55C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? \e[32mShow diff\e[0m\n",
"\e[32m>> \e[0mShow diff",
"\e[A\e[1G\e[28C\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "informs about invalid input when automatically expanding hint" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keybackspace) if e.value == "w" }
prompt.input << "y" << "y" << "\u007F" << "\r"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?', choices, defualt: 1, auto_hint: true)
expect(result).to eq(:yes)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\n\e[32m>> \e[0mOverwrite",
"\e[A\e[1G\e[54C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"y\n",
"\e[32m>> \e[0mOverwrite",
"\e[A\e[1G\e[55C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [y,n,a,d,q,h] ",
"yy\n",
"\e[32m>> \e[0minvalid option",
"\e[A\e[1G\e[56C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"y\n",
"\e[32m>> \e[0mOverwrite",
"\e[A\e[1G\e[55C",
"\e[2K\e[1G",
"\e[1B",
"\e[2K\e[1G",
"\e[A\e[1G",
"Overwrite Gemfile? \e[32mOverwrite\e[0m\n",
"\e[32m>> \e[0mOverwrite",
"\e[A\e[1G\e[28C\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "specifies options through DSL" do
prompt.input << "\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?') do |q|
q.default 4
q.choice key: 'y', name: 'Overwrite', value: :yes
q.choice key: 'n', name: 'Skip', value: :no
q.choice key: 'a', name: 'Overwrite all', value: :all
q.choice key: 'd', name: 'Show diff', value: :diff
q.choice key: 'q', name: 'Quit', value: :quit
end
expect(result).to eq(:diff)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [y,n,a,\e[32md\e[0m,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? \e[32mShow diff\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "specifies options through DSL and executes value" do
prompt.input << "\n"
prompt.input.rewind
result = prompt.expand('Overwrite Gemfile?') do |q|
q.choice key: 'y', name: 'Overwrite' do :ok end
q.choice key: 'n', name: 'Skip', value: :no
q.choice key: 'a', name: 'Overwrite all', value: :all
q.choice key: 'd', name: 'Show diff', value: :diff
q.choice key: 'q', name: 'Quit', value: :quit
end
expect(result).to eq(:ok)
expected_output = [
"Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
"\e[2K\e[1G",
"Overwrite Gemfile? \e[32mOverwrite\e[0m\n"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "fails to expand due to lack of key attribute" do
choices = [{ name: 'Overwrite', value: :yes }]
expect {
prompt.expand('Overwrite Gemfile?', choices)
}.to raise_error(TTY::Prompt::ConfigurationError, /Choice Overwrite is missing a :key attribute/)
end
it "fails to expand due to wrong key length" do
choices = [{ key: 'long', name: 'Overwrite', value: :yes }]
expect {
prompt.expand('Overwrite Gemfile?', choices)
}.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `long` is more than one character long/)
end
it "fails to expand due to reserve key" do
choices = [{ key: 'h', name: 'Overwrite', value: :yes }]
expect {
prompt.expand('Overwrite Gemfile?', choices)
}.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `h` is reserved for help menu/)
end
it "fails to expand due to duplicate key" do
choices = [{ key: 'y', name: 'Overwrite', value: :yes },
{ key: 'y', name: 'Change', value: :yes }]
expect {
prompt.expand('Overwrite Gemfile?', choices)
}.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `y` is a duplicate/)
end
end
tty-prompt-0.21.0/spec/unit/keypress_spec.rb 0000664 0000000 0000000 00000003106 13631250432 0021045 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#keypress' do
it 'receives line feed with echo on' do
prompt = TTY::TestPrompt.new
prompt.input << "\n"
prompt.input.rewind
answer = prompt.keypress("Press key:", echo: true)
expect(answer).to eq("\n")
expect(prompt.output.string).to eq([
"Press key: ",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Press key: \e[32m\n\e[0m\n",
].join)
end
it 'asks for a keypress with echo on' do
prompt = TTY::TestPrompt.new
prompt.input << "abcd"
prompt.input.rewind
answer = prompt.keypress("Press key:", echo: true)
expect(answer).to eq("a")
expect(prompt.output.string).to eq([
"Press key: ",
"\e[2K\e[1G",
"Press key: \e[32ma\e[0m\n",
].join)
end
it 'asks for a keypress with echo off' do
prompt = TTY::TestPrompt.new
prompt.input << "abcd"
prompt.input.rewind
answer = prompt.keypress("Press key:")
expect(answer).to eq("a")
expect(prompt.output.string).to eq([
"Press key: ",
"\e[2K\e[1G",
"Press key: \n",
].join)
end
it "interrupts input" do
prompt = TTY::TestPrompt.new(interrupt: :exit)
prompt.input << "\x03"
prompt.input.rewind
expect {
prompt.keypress("Press key:")
}.to raise_error(SystemExit)
end
it "timeouts when no key provided" do
prompt = TTY::TestPrompt.new(interrupt: :exit)
prompt.keypress("Press any key or continue in :countdown", timeout: 0.01)
expect(prompt.output.string).to include("Press any key or continue in 0.00")
end
end
tty-prompt-0.21.0/spec/unit/mask_spec.rb 0000664 0000000 0000000 00000007417 13631250432 0020144 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#mask' do
subject(:prompt) { TTY::TestPrompt.new }
let(:symbols) { TTY::Prompt::Symbols.symbols }
it "masks output by default" do
prompt.input << "pass\r"
prompt.input.rewind
answer = prompt.mask("What is your password?")
expect(answer).to eql("pass")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1G",
"What is your password? #{symbols[:dot]}",
"\e[2K\e[1G",
"What is your password? #{symbols[:dot] * 2}",
"\e[2K\e[1G",
"What is your password? #{symbols[:dot] * 3}",
"\e[2K\e[1G",
"What is your password? #{symbols[:dot] * 4}",
"\e[2K\e[1G",
"What is your password? \e[32m#{symbols[:dot] * 4}\e[0m\n",
"\e[1A\e[2K\e[1G",
"What is your password? \e[32m#{symbols[:dot] * 4}\e[0m\n"
].join)
end
it 'masks output with custom character' do
prompt.input << "pass\r"
prompt.input.rewind
answer = prompt.mask("What is your password?") { |q| q.mask('*') }
expect(answer).to eql("pass")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1G",
"What is your password? *",
"\e[2K\e[1G",
"What is your password? **",
"\e[2K\e[1G",
"What is your password? ***",
"\e[2K\e[1G",
"What is your password? ****",
"\e[2K\e[1G",
"What is your password? \e[32m****\e[0m\n",
"\e[1A\e[2K\e[1G",
"What is your password? \e[32m****\e[0m\n",
].join)
end
it "masks with unicode character" do
prompt.input << "lov\n"
prompt.input.rewind
answer = prompt.mask("What is your password?", mask: "\u2665")
expect(answer).to eql("lov")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1G",
"What is your password? ♥",
"\e[2K\e[1G",
"What is your password? ♥♥",
"\e[2K\e[1G",
"What is your password? ♥♥♥",
"\e[2K\e[1G",
"What is your password? \e[32m♥♥♥\e[0m\n",
"\e[1A\e[2K\e[1G",
"What is your password? \e[32m♥♥♥\e[0m\n",
].join)
end
it 'ignores mask if echo is off' do
prompt.input << "pass\n"
prompt.input.rewind
answer = prompt.mask('What is your password?') do |q|
q.echo false
q.mask '*'
end
expect(answer).to eql("pass")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1G",
"What is your password? ",
"\e[2K\e[1G",
"What is your password? ",
"\e[2K\e[1G",
"What is your password? ",
"\e[2K\e[1G",
"What is your password? ",
"\e[2K\e[1G",
"What is your password? \n",
"\e[1A\e[2K\e[1G",
"What is your password? \n",
].join)
end
it "validates input" do
prompt = TTY::TestPrompt.new(symbols: {dot: '*'})
prompt.input << "no\nyes\n"
prompt.input.rewind
answer = prompt.mask('What is your password?') do |q|
q.echo true
q.validate(/[a-z]{3,4}/)
q.messages[:valid?] = 'Not valid'
end
expect(answer).to eq('yes')
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1G",
"What is your password? *",
"\e[2K\e[1G",
"What is your password? **",
"\e[2K\e[1G",
"What is your password? \e[32m**\e[0m\n",
"\e[31m>>\e[0m Not valid",
"\e[1A\e[2K\e[1G",
"What is your password? \e[31m**\e[0m",
"\e[2K\e[1G",
"What is your password? *",
"\e[2K\e[1G",
"What is your password? **",
"\e[2K\e[1G",
"What is your password? ***",
"\e[2K\e[1G",
"What is your password? \e[32m***\e[0m\n",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"What is your password? \e[32m***\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/multi_select_spec.rb 0000664 0000000 0000000 00000072407 13631250432 0021703 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt do
let(:symbols) { TTY::Prompt::Symbols.symbols }
let(:up_down) { "#{symbols[:arrow_up]}/#{symbols[:arrow_down]}" }
let(:left_right) { "#{symbols[:arrow_left]}/#{symbols[:arrow_right]}"}
def output_helper(prompt, choices, active, selected, options = {})
raise ":init requires :hint" if options[:init] && options[:hint].nil?
hint = options[:hint]
init = options.fetch(:init, false)
enum = options[:enum]
out = []
out << "\e[?25l" if init
out << prompt << " "
out << "(min. #{options[:min]}) " if options[:min]
out << "(max. #{options[:max]}) " if options[:max]
out << selected.join(', ')
out << " " if init && !selected.empty?
out << "\e[90m" if init
out << (init ? "(#{hint})\e[0m" : " (#{hint})") if hint
out << "\n"
out << choices.map.with_index do |choice, i|
name = choice.is_a?(Hash) ? choice[:name] : choice
disabled = choice.is_a?(Hash) ? choice[:disabled] : false
num = (i + 1).to_s + enum if enum
prefix = name == active ? "#{symbols[:marker]} " : " "
prefix += if disabled
"\e[31m#{symbols[:cross]}\e[0m #{num}#{name} #{disabled}"
elsif selected.include?(name)
"\e[32m#{symbols[:radio_on]}\e[0m #{num}#{name}"
else
"#{symbols[:radio_off]} #{num}#{name}"
end
prefix
end.join("\n")
out << "\e[2K\e[1G\e[1A" * choices.count
out << "\e[2K\e[1G"
out.join
end
def exit_message(prompt, choices)
"#{prompt} \e[32m#{choices.join(', ')}\e[0m\n\e[?25h"
end
# Ensure a wide prompt on CI
before { allow(TTY::Screen).to receive(:width).and_return(200) }
it "selects nothing when return pressed immediately" do
prompt = TTY::TestPrompt.new
choices = %i(vodka beer wine whisky bourbon)
prompt.input << "\r"
prompt.input.rewind
expect(prompt.multi_select("Select drinks?", choices)). to eq([])
expected_output = [
"\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "selects item when space pressed" do
prompt = TTY::TestPrompt.new
choices = %w(vodka beer wine whisky bourbon)
prompt.input << " \r"
prompt.input.rewind
expect(prompt.multi_select("Select drinks?", choices)). to eq(['vodka'])
expected_output = [
"\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? vodka\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \e[32mvodka\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "selects item when space pressed but doesn't echo item if echo: false" do
prompt = TTY::TestPrompt.new
choices = %w(vodka beer wine whisky bourbon)
prompt.input << " \r"
prompt.input.rewind
expect(prompt.multi_select("Select drinks?", choices, echo: false)). to eq(['vodka'])
expected_output = [
"\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "sets choice custom values" do
prompt = TTY::TestPrompt.new
choices = {vodka: 1, beer: 2, wine: 3, whisky: 4, bourbon: 5}
prompt.input << " \r"
prompt.input.rewind
expect(prompt.multi_select("Select drinks?", choices)).to eq([1])
expected_output = [
"\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? vodka\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \e[32mvodka\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "sets choice name and value through DSL" do
prompt = TTY::TestPrompt.new
prompt.input << " \r"
prompt.input.rewind
value = prompt.multi_select("Select drinks?") do |menu|
menu.symbols marker: '>', radio_off: '-', radio_on: '='
menu.enum ')'
menu.choice :vodka, {score: 1}
menu.choice :beer, 2
menu.choice :wine, 3
menu.choices whisky: 4, bourbon: 5
end
expect(value).to eq([{score: 1}])
expect(prompt.output.string).to eq([
"\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow or number (1-5) keys, press Space to select and Enter to finish)\e[0m\n",
"> - 1) vodka\n",
" - 2) beer\n",
" - 3) wine\n",
" - 4) whisky\n",
" - 5) bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? vodka\n",
"> \e[32m=\e[0m 1) vodka\n",
" - 2) beer\n",
" - 3) wine\n",
" - 4) whisky\n",
" - 5) bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \e[32mvodka\e[0m\n\e[?25h"
].join)
end
it "sets default options through DSL syntax" do
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
value = prompt.multi_select("Select drinks?") do |menu|
menu.default 2, 5
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
expect(value).to match_array([{score: 20}, {score: 50}])
expect(prompt.output.string).to eq([
"\e[?25lSelect drinks? beer, bourbon \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
" #{symbols[:radio_off]} vodka\n",
" \e[32m#{symbols[:radio_on]}\e[0m beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \e[32mbeer, bourbon\e[0m\n\e[?25h",
].join)
end
it "sets default options through hash syntax" do
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
value = prompt.multi_select("Select drinks?", default: [2, 5]) do |menu|
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
expect(value).to match_array([{score: 20}, {score: 50}])
end
it "raises error for defaults out of range" do
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
expect {
prompt.multi_select("Select drinks?", default: [2, 6]) do |menu|
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
}.to raise_error(TTY::Prompt::ConfigurationError,
/default index `6` out of range \(1 - 5\)/)
end
it "sets prompt prefix" do
prompt = TTY::TestPrompt.new(prefix: '[?] ')
choices = %w(vodka beer wine whisky bourbon)
prompt.input << "\r"
prompt.input.rewind
expect(prompt.multi_select("Select drinks?", choices)). to eq([])
expect(prompt.output.string).to eq([
"\e[?25l[?] Select drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"[?] Select drinks? \n\e[?25h"
].join)
end
it "changes selected item color & marker" do
prompt = TTY::TestPrompt.new
choices = %w(vodka beer wine whisky bourbon)
prompt.input << "\r"
prompt.input.rewind
options = {default: [1], active_color: :blue, symbols: {marker: '>'}}
expect(prompt.multi_select("Select drinks?", choices, options)). to eq(['vodka'])
expect(prompt.output.string).to eq([
"\e[?25lSelect drinks? vodka \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"> \e[34m#{symbols[:radio_on]}\e[0m vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \e[34mvodka\e[0m\n\e[?25h"
].join)
end
it "changes help text and color" do
prompt = TTY::TestPrompt.new
choices = %w(vodka beer wine whisky bourbon)
prompt.input << "\r"
prompt.input.rewind
options = { help: "(Bash keyboard)", help_color: :cyan }
answer = prompt.multi_select("Select drinks?", choices, options)
expect(answer).to eq([])
expected_output = [
"\e[?25lSelect drinks? \e[36m(Bash keyboard)\e[0m\n",
"#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
" #{symbols[:radio_off]} beer\n",
" #{symbols[:radio_off]} wine\n",
" #{symbols[:radio_off]} whisky\n",
" #{symbols[:radio_off]} bourbon",
"\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
"Select drinks? \n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
context "when paginated" do
it "paginates long selections" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F G H)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.multi_select("What letter?", choices, per_page: 3, default: 4)
expect(answer).to eq(['D'])
expected_output = [
"\e[?25lWhat letter? D \e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
" #{symbols[:radio_off]} E\n",
" #{symbols[:radio_off]} F",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "paginates choices as hash object" do
prompt = TTY::TestPrompt.new
choices = {A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8}
prompt.input << "\r"
prompt.input.rewind
answer = prompt.multi_select("What letter?", choices, default: 4, per_page: 3)
expect(answer).to eq([4])
expected_output = [
"\e[?25lWhat letter? D ",
"\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
" #{symbols[:radio_off]} E\n",
" #{symbols[:radio_off]} F",
"\e[2K\e[1G\e[1A" * 3, "\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "paginates long selections through DSL" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F G H)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.multi_select("What letter?") do |menu|
menu.per_page 3
menu.default 4
menu.choices choices
end
expect(answer).to eq(['D'])
expected_output = [
"\e[?25lWhat letter? D ",
"\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
" #{symbols[:radio_off]} E\n",
" #{symbols[:radio_off]} F",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "doesn't paginate short selections" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D)
prompt.input << "\r"
prompt.input.rewind
value = prompt.multi_select("What letter?", choices, per_page: 4, default: 1)
expect(value).to eq(['A'])
expect(prompt.output.string).to eq([
"\e[?25lWhat letter? A ",
"\e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m A\n",
" #{symbols[:radio_off]} B\n",
" #{symbols[:radio_off]} C\n",
" #{symbols[:radio_off]} D",
"\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
"What letter? \e[32mA\e[0m\n\e[?25h",
].join)
end
it "navigates evenly paged output with right arrow until end of selection" do
prompt = TTY::TestPrompt.new
choices = ('1'..'12').to_a
prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
prompt.input << "l" << "l" << "l" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What number?", choices, per_page: 4)
expect(answer).to eq(["9"])
expected_output = [
output_helper("What number?", choices[0..3], "1", [], init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
output_helper("What number?", choices[4..7], "5", []),
output_helper("What number?", choices[8..11], "9", []),
output_helper("What number?", choices[8..11], "9", []),
output_helper("What number?", choices[8..11], "9", ["9"]),
"What number? \e[32m9\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates unevenly paged output with right arrow until the end of selection" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
prompt.input << "l" << "l" << "l" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What number?", choices, default: 4, per_page: 4)
expect(answer).to eq(['4', '10'])
expected_output = [
output_helper("What number?", choices[3..6], "4", ["4"], init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
output_helper("What number?", choices[4..7], "8", ["4"]),
output_helper("What number?", choices[8..9], "10", ["4"]),
output_helper("What number?", choices[8..9], "10", ["4"]),
output_helper("What number?", choices[8..9], "10", ["4", "10"]),
"What number? \e[32m4, 10\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates left and right" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "l" << "l" << "h" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4)
expect(answer).to eq(['2', '6'])
expected_output = [
output_helper("What number?", choices[0..3], "2", ["2"], init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
output_helper("What number?", choices[4..7], "6", ["2"]),
output_helper("What number?", choices[8..9], "10", ["2"]),
output_helper("What number?", choices[4..7], "6", ["2"]),
output_helper("What number?", choices[4..7], "6", ["2", "6"]),
"What number? \e[32m2, 6\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "combines up/down navigation with left/right" do
prompt = TTY::TestPrompt.new
choices = ('1'..'11').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyup) if e.value == "k"
prompt.trigger(:keydown) if e.value == "j"
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "j" << "l" << "k" << "k" << "h" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4)
expect(answer).to eq(['2', '1'])
expected_output = [
output_helper("What number?", choices[0..3], "2", ["2"], init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
output_helper("What number?", choices[0..3], "3", ["2"]),
output_helper("What number?", choices[4..7], "7", ["2"]),
output_helper("What number?", choices[4..7], "6", ["2"]),
output_helper("What number?", choices[3..6], "5", ["2"]),
output_helper("What number?", choices[0..3], "1", ["2"]),
output_helper("What number?", choices[0..3], "1", ["2", "1"]),
"What number? \e[32m2, 1\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
end
context "with :cycle" do
it "doesn't cycle by default" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << "j" << "j" << " " << "\r"
prompt.input.rewind
value = prompt.multi_select("What letter?", choices)
expect(value).to eq(["C"])
expect(prompt.output.string).to eq(
output_helper("What letter?", choices, "A", [], init: true,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
output_helper("What letter?", choices, "B", []) +
output_helper("What letter?", choices, "C", []) +
output_helper("What letter?", choices, "C", []) +
output_helper("What letter?", choices, "C", ["C"]) +
"What letter? \e[32mC\e[0m\n\e[?25h"
)
end
it "cycles when configured to do so" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << "j" << "j" << " " << "\r"
prompt.input.rewind
value = prompt.multi_select("What letter?", choices, cycle: true)
expect(value).to eq(["A"])
expect(prompt.output.string).to eq(
output_helper("What letter?", choices, "A", [], init: true,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
output_helper("What letter?", choices, "B", []) +
output_helper("What letter?", choices, "C", []) +
output_helper("What letter?", choices, "A", []) +
output_helper("What letter?", choices, "A", ["A"]) +
"What letter? \e[32mA\e[0m\n\e[?25h"
)
end
it "cycles choices using left/right arrows" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "l" << "l" << "l" << "h" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4, cycle: true)
expect(answer).to eq(['2', '10'])
expected_output = [
"\e[?25lWhat number? 2 ",
"\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
" #{symbols[:radio_off]} 1\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 2\n",
" #{symbols[:radio_off]} 3\n",
" #{symbols[:radio_off]} 4",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G",
"What number? 2\n",
" #{symbols[:radio_off]} 5\n",
"#{symbols[:marker]} #{symbols[:radio_off]} 6\n",
" #{symbols[:radio_off]} 7\n",
" #{symbols[:radio_off]} 8",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G",
"What number? 2\n",
" #{symbols[:radio_off]} 9\n",
"#{symbols[:marker]} #{symbols[:radio_off]} 10",
"\e[2K\e[1G\e[1A" * 2,
"\e[2K\e[1G",
"What number? 2\n",
" #{symbols[:radio_off]} 1\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 2\n",
" #{symbols[:radio_off]} 3\n",
" #{symbols[:radio_off]} 4",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G",
"What number? 2\n",
" #{symbols[:radio_off]} 9\n",
"#{symbols[:marker]} #{symbols[:radio_off]} 10",
"\e[2K\e[1G\e[1A" * 2,
"\e[2K\e[1G",
"What number? 2, 10\n",
" #{symbols[:radio_off]} 9\n",
"#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 10",
"\e[2K\e[1G\e[1A" * 2,
"\e[2K\e[1G",
"What number? \e[32m2, 10\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
end
context "with filter" do
it "doesn't lose the selection when switching between filters" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keydelete) if e.value == "\r" }
prompt.input << " " # select `Tiny`
prompt.input << "a" << " " # match and select `Large`
prompt.input << "\u007F" # backspace (shows all)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.multi_select("What size?", %w(Tiny Medium Large Huge), filter: true)
expect(answer).to eql(%w(Tiny Large))
expected_prompt_output =
output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(), init: true,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish, and letter keys to filter") +
output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(Tiny)) +
output_helper("What size?", %w(Large), "Large", %w(Tiny), hint: 'Filter: "a"') +
output_helper("What size?", %w(Large), "Large", %w(Tiny Large), hint: 'Filter: "a"') +
output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(Tiny Large)) +
exit_message("What size?", %w(Tiny Large))
expect(prompt.output.string).to eql(expected_prompt_output)
end
end
context "with :disabled" do
it "fails when default item is also disabled" do
prompt = TTY::TestPrompt.new
choices = [
{name: 'vodka', disabled: true},
'beer', 'wine', 'whisky', 'bourbon'
]
expect {
prompt.multi_select("Select drinks?", choices, default: 1)
}.to raise_error(TTY::Prompt::ConfigurationError,
/default index `1` matches disabled choice item/)
end
it "adjusts active index to match first non-disabled choice" do
choices = [
{name: 'vodka', disabled: true},
'beer', 'wine', 'whisky', 'bourbon'
]
prompt = TTY::TestPrompt.new
prompt.input << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("Select drinks?", choices)
expect(answer).to eq(['beer'])
end
it "omits disabled choice when nagivating menu" do
choices = [
{name: 'A'},
{name: 'B', disabled: '(out)'},
{name: 'C', disabled: '(out)'},
{name: 'D'},
{name: 'E'}
]
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << " " << "j" << " " << "\r"
prompt.input.rewind
answer = prompt.multi_select("What letter?", choices)
expect(answer).to eq(%w[D E])
expected_output =
output_helper("What letter?", choices, "A", [], init: true,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
output_helper("What letter?", choices, "D", []) +
output_helper("What letter?", choices, "D", %w[D]) +
output_helper("What letter?", choices, "E", %w[D]) +
output_helper("What letter?", choices, "E", %w[D E]) +
exit_message("What letter?", %w[D E])
expect(prompt.output.string).to eq(expected_output)
end
it "omits disabled choice when number key is pressed" do
choices = [
{name: 'vodka', value: 1},
{name: 'beer', value: 1, disabled: true},
{name: 'wine', value: 1},
{name: 'whisky', value: 1, disabled: true},
{name: 'bourbon', value: 1}
]
prompt = TTY::TestPrompt.new
prompt.input << "2" << " \r"
prompt.input.rewind
answer = prompt.multi_select("Select drinks?") do |menu|
menu.enum ')'
menu.choice :vodka, 1
menu.choice :beer, 2, disabled: true
menu.choice :wine, 3
menu.choice :whisky, 4, disabled: true
menu.choice :bourbon, 5
end
expect(answer).to eq([1])
expected_output =
output_helper("Select drinks?", choices, "vodka", [], init: true, enum: ') ',
hint: "Use #{up_down} arrow or number (1-5) keys, press Space to select and Enter to finish") +
output_helper("Select drinks?", choices, "vodka", [], enum: ') ') +
output_helper("Select drinks?", choices, "vodka", %w[vodka], enum: ') ') +
exit_message("Select drinks?", %w[vodka])
expect(prompt.output.string).to eq(expected_output)
end
end
context "with :min" do
it "requires number of choices" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e|
prompt.trigger(:keydown) if e.value == "j"
}
prompt.input << " " << "\r" << "j" << " " << "\r"
prompt.input.rewind
value = prompt.multi_select("What letter?", choices, min: 2, per_page: 100)
expect(value).to eq(["A", "B"])
expected_output =
output_helper("What letter?", choices, "A", [], init: true, min: 2,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
output_helper("What letter?", choices, "A", %w[A], min: 2) +
output_helper("What letter?", choices, "A", %w[A], min: 2) +
output_helper("What letter?", choices, "B", %w[A], min: 2) +
output_helper("What letter?", choices, "B", %w[A B], min: 2) +
exit_message("What letter?", %w[A B])
expect(prompt.output.string).to eq(expected_output)
end
end
context "with :max" do
it "limits number of choices" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e|
prompt.trigger(:keyup) if e.value == "k"
prompt.trigger(:keydown) if e.value == "j"
}
prompt.input << " " << "j" << " " << "j" << " " << "k" << " " << "j" << " " << "\r"
prompt.input.rewind
value = prompt.multi_select("What letter?", choices, max: 2, per_page: 100)
expect(value).to eq(["A", "C"])
expected_output =
output_helper("What letter?", choices, "A", [], init: true, max: 2,
hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
output_helper("What letter?", choices, "A", %w[A], max: 2) +
output_helper("What letter?", choices, "B", %w[A], max: 2) +
output_helper("What letter?", choices, "B", %w[A B], max: 2) +
output_helper("What letter?", choices, "C", %w[A B], max: 2) +
output_helper("What letter?", choices, "C", %w[A B], max: 2) +
output_helper("What letter?", choices, "B", %w[A B], max: 2) +
output_helper("What letter?", choices, "B", %w[A], max: 2) +
output_helper("What letter?", choices, "C", %w[A], max: 2) +
output_helper("What letter?", choices, "C", %w[A C], max: 2) +
exit_message("What letter?", %w[A C])
expect(prompt.output.string).to eq(expected_output)
end
end
end
tty-prompt-0.21.0/spec/unit/multiline_spec.rb 0000664 0000000 0000000 00000004104 13631250432 0021201 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#multiline' do
it 'reads no lines' do
prompt = TTY::TestPrompt.new
prompt.input << "\C-d"
prompt.input.rewind
answer = prompt.multiline("Description?")
expect(answer).to eq([])
expect(prompt.output.string).to eq([
"Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Description? \n"
].join)
end
it "uses defualt when no input" do
prompt = TTY::TestPrompt.new
prompt.input << "\C-d"
prompt.input.rewind
answer = prompt.multiline("Description?", default: 'A super sweet prompt')
expect(answer).to eq([])
expect(prompt.output.string).to eq([
"Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Description? \e[32mA super sweet prompt\e[0m\n"
].join)
end
it "changes help text" do
prompt = TTY::TestPrompt.new
prompt.input << "\C-d"
prompt.input.rewind
answer = prompt.multiline("Description?") do |q|
q.default 'A super sweet prompt'
q.help '(Press thy ctrl-d to end)'
end
expect(answer).to eq([])
expect(prompt.output.string).to eq([
"Description? \e[90m(Press thy ctrl-d to end)\e[0m\n",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Description? \e[32mA super sweet prompt\e[0m\n"
].join)
end
it 'reads multiple lines with empty lines' do
prompt = TTY::TestPrompt.new
prompt.input << "aa\n\nbb\n\n\ncc\C-d"
prompt.input.rewind
answer = prompt.multiline("Description?")
expect(answer).to eq(["aa\n", "bb\n", "cc"])
expect(prompt.output.string).to eq([
"Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
"\e[2K\e[1Ga",
"\e[2K\e[1Gaa",
"\e[2K\e[1Gaa\n",
"\e[2K\e[1G\n",
"\e[2K\e[1Gb",
"\e[2K\e[1Gbb",
"\e[2K\e[1Gbb\n",
"\e[2K\e[1G\n",
"\e[2K\e[1G\n",
"\e[2K\e[1Gc",
"\e[2K\e[1Gcc",
"\e[2K\e[1G\e[1A" * 6,
"\e[2K\e[1G",
"Description? \e[32maa ...\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/new_spec.rb 0000664 0000000 0000000 00000001004 13631250432 0017764 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#new' do
let(:env) { { "TTY_TEST" => true } }
it "sets prefix" do
prompt = described_class.new(prefix: "[?]", env: env)
expect(prompt.prefix).to eq("[?]")
end
it "sets input stream" do
prompt = described_class.new(input: :stream1, env: env)
expect(prompt.input).to eq(:stream1)
end
it "sets output stream" do
prompt = described_class.new(output: :stream2, env: env)
expect(prompt.output).to eq(:stream2)
end
end
tty-prompt-0.21.0/spec/unit/ok_spec.rb 0000664 0000000 0000000 00000000370 13631250432 0017611 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, 'ok' do
subject(:prompt) { TTY::TestPrompt.new }
it 'prints text in green' do
prompt.ok("All is fine")
expect(prompt.output.string).to eq("\e[32mAll is fine\e[0m\n")
end
end
tty-prompt-0.21.0/spec/unit/paginator_spec.rb 0000664 0000000 0000000 00000006402 13631250432 0021166 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Paginator, '#paginate' do
it "ignores per_page when equal items " do
list = %w(a b c d)
paginator = described_class.new(per_page: 4)
expect(paginator.paginate(list, 1).to_a).to eq([
['a',0],['b',1],['c',2],['d',3]])
end
it "ignores per_page when less items " do
list = %w(a b c d)
paginator = described_class.new(per_page: 5)
expect(paginator.paginate(list, 1).to_a).to eq([
['a',0],['b',1],['c',2],['d',3]])
end
it "paginates items matching per_page count" do
list = %w(a b c d e f)
paginator = described_class.new(per_page: 3)
expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 3).to_a).to eq([['b',1], ['c',2], ['d',3]])
expect(paginator.paginate(list, 4).to_a).to eq([['c',2], ['d',3], ['e',4]])
expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 7).to_a).to eq([['d',3], ['e',4], ['f',5]])
end
it "paginates items not matching per_page count" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3)
expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
expect(paginator.paginate(list, 3).to_a).to eq([['b',1], ['c',2], ['d',3]])
expect(paginator.paginate(list, 4).to_a).to eq([['c',2], ['d',3], ['e',4]])
expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
expect(paginator.paginate(list, 6).to_a).to eq([['e',4], ['f',5], ['g',6]])
expect(paginator.paginate(list, 7).to_a).to eq([['e',4], ['f',5], ['g',6]])
expect(paginator.paginate(list, 8).to_a).to eq([['e',4], ['f',5], ['g',6]])
end
it "finds both start and end index for current selection" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3, default: 0)
paginator.paginate(list, 2)
expect(paginator.start_index).to eq(0)
expect(paginator.end_index).to eq(2)
paginator.paginate(list, 3)
expect(paginator.start_index).to eq(1)
expect(paginator.end_index).to eq(3)
paginator.paginate(list, 4)
expect(paginator.start_index).to eq(2)
expect(paginator.end_index).to eq(4)
paginator.paginate(list, 5)
expect(paginator.start_index).to eq(3)
expect(paginator.end_index).to eq(5)
paginator.paginate(list, 7)
expect(paginator.start_index).to eq(4)
expect(paginator.end_index).to eq(6)
paginator.paginate(list, 8)
expect(paginator.start_index).to eq(4)
expect(paginator.end_index).to eq(6)
end
it "starts with default selection" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 3, default: 3)
expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
end
it "doesn't accept invalid pagination" do
list = %w(a b c d e f g)
paginator = described_class.new(per_page: 0)
expect {
paginator.paginate(list, 4)
}.to raise_error(TTY::Prompt::InvalidArgument, /per_page must be > 0/)
end
end
tty-prompt-0.21.0/spec/unit/question/ 0000775 0000000 0000000 00000000000 13631250432 0017510 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/question/checks_spec.rb 0000664 0000000 0000000 00000005532 13631250432 0022314 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question do
subject(:prompt) { TTY::TestPrompt.new }
it "passes range check" do
question = described_class.new(prompt)
question.in 1..10
result = TTY::Prompt::Question::Checks::CheckRange.call(question, 2)
expect(result).to eq([2])
end
it "fails range check" do
question = described_class.new(prompt, messages: TTY::Prompt.messages)
question.in 1..10
result = TTY::Prompt::Question::Checks::CheckRange.call(question, 11)
expect(result).to eq([11, ["Value 11 must be within the range 1..10"]])
end
it "fails range check" do
question = described_class.new(prompt)
question.in 1..10, 'Outside of range!'
result = TTY::Prompt::Question::Checks::CheckRange.call(question, 11)
expect(result).to eq([11, ['Outside of range!']])
end
it "passes validation check" do
question = described_class.new(prompt)
question.validate(/\A\d{5}\Z/)
result = TTY::Prompt::Question::Checks::CheckValidation.call(question, '12345')
expect(result).to eq(['12345'])
end
it "fails validation check" do
question = described_class.new(prompt, messages: TTY::Prompt.messages)
question.validate(/\A\d{5}\Z/)
result = TTY::Prompt::Question::Checks::CheckValidation.call(question, '123')
expect(result).to eq(['123', ['Your answer is invalid (must match /\\A\\d{5}\\Z/)']])
end
it "fails validation check with inlined custom message" do
question = described_class.new(prompt)
question.validate(/\A\w+@\w+\.\w+\Z/, 'Invalid email address')
result = TTY::Prompt::Question::Checks::CheckValidation.call(question, 'piotr@com')
expect(result).to eq(['piotr@com', ['Invalid email address']])
end
it "fails validation check with custom message" do
question = described_class.new(prompt)
question.validate(/\A\w+@\w+\.\w+\Z/)
question.messages[:valid?] = 'Invalid email address'
result = TTY::Prompt::Question::Checks::CheckValidation.call(question, 'piotr@com')
expect(result).to eq(['piotr@com', ['Invalid email address']])
end
it "passes required check" do
question = described_class.new(prompt)
question.required true
result = TTY::Prompt::Question::Checks::CheckRequired.call(question, 'Piotr')
expect(result).to eq(['Piotr'])
end
it "fails required check" do
question = described_class.new(prompt, messages: TTY::Prompt.messages)
question.required true
result = TTY::Prompt::Question::Checks::CheckRequired.call(question, nil)
expect(result).to eq([nil, ['Value must be provided']])
end
it "fails required check with custom message" do
question = described_class.new(prompt)
question.required true, 'Required input'
result = TTY::Prompt::Question::Checks::CheckRequired.call(question, nil)
expect(result).to eq([nil, ['Required input']])
end
end
tty-prompt-0.21.0/spec/unit/question/default_spec.rb 0000664 0000000 0000000 00000001634 13631250432 0022477 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#default' do
subject(:prompt) { TTY::TestPrompt.new }
it 'uses default value' do
name = 'Anonymous'
prompt.input << "\n"
prompt.input.rewind
answer = prompt.ask('What is your name?', default: name)
expect(answer).to eq(name)
expect(prompt.output.string).to eq([
"What is your name? \e[90m(Anonymous)\e[0m ",
"\e[2K\e[1GWhat is your name? \e[90m(Anonymous)\e[0m \n",
"\e[1A\e[2K\e[1G",
"What is your name? \e[32mAnonymous\e[0m\n"
].join)
end
it 'uses default value in block' do
name = 'Anonymous'
answer = prompt.ask('What is your name?') { |q| q.default(name) }
expect(answer).to eq(name)
expect(prompt.output.string).to eq([
"What is your name? \e[90m(Anonymous)\e[0m ",
"\e[1A\e[2K\e[1G",
"What is your name? \e[32mAnonymous\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/question/echo_spec.rb 0000664 0000000 0000000 00000002270 13631250432 0021766 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#echo' do
subject(:prompt) { TTY::TestPrompt.new }
it 'asks with echo on' do
prompt.input << "password"
prompt.input.rewind
answer = prompt.ask("What is your password?") { |q| q.echo(true) }
expect(answer).to eql("password")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[2K\e[1GWhat is your password? p",
"\e[2K\e[1GWhat is your password? pa",
"\e[2K\e[1GWhat is your password? pas",
"\e[2K\e[1GWhat is your password? pass",
"\e[2K\e[1GWhat is your password? passw",
"\e[2K\e[1GWhat is your password? passwo",
"\e[2K\e[1GWhat is your password? passwor",
"\e[2K\e[1GWhat is your password? password",
"\e[1A\e[2K\e[1G",
"What is your password? \e[32mpassword\e[0m\n"
].join)
end
it 'asks with echo off' do
prompt.input << "password"
prompt.input.rewind
answer = prompt.ask("What is your password?", echo: false)
expect(answer).to eql("password")
expect(prompt.output.string).to eq([
"What is your password? ",
"\e[1A\e[2K\e[1G",
"What is your password? \n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/question/in_spec.rb 0000664 0000000 0000000 00000006370 13631250432 0021463 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#in' do
subject(:prompt) { TTY::TestPrompt.new }
it "reads range from option" do
prompt.input << '8'
prompt.input.rewind
answer = prompt.ask("How do you like it on scale 1-10?", in: '1-10')
expect(answer).to eq('8')
end
it 'reads number within string range' do
prompt.input << '8'
prompt.input.rewind
answer = prompt.ask("How do you like it on scale 1-10?") do |q|
q.in('1-10')
end
expect(answer).to eq('8')
expect(prompt.output.string).to eq([
"How do you like it on scale 1-10? ",
"\e[2K\e[1GHow do you like it on scale 1-10? 8",
"\e[1A\e[2K\e[1G",
"How do you like it on scale 1-10? \e[32m8\e[0m\n",
].join)
end
it 'reads number within digit range' do
prompt.input << '8.1'
prompt.input.rewind
answer = prompt.ask("How do you like it on scale 1-10?") do |q|
q.in(1.0..11.5)
end
expect(answer).to eq('8.1')
expect(prompt.output.string).to eq([
"How do you like it on scale 1-10? ",
"\e[2K\e[1GHow do you like it on scale 1-10? 8",
"\e[2K\e[1GHow do you like it on scale 1-10? 8.",
"\e[2K\e[1GHow do you like it on scale 1-10? 8.1",
"\e[1A\e[2K\e[1G",
"How do you like it on scale 1-10? \e[32m8.1\e[0m\n",
].join)
end
it 'reads letters within range' do
prompt.input << 'E'
prompt.input.rewind
answer = prompt.ask("Your favourite vitamin? (A-K)") do |q|
q.in('A-K')
end
expect(answer).to eq('E')
expect(prompt.output.string).to eq([
"Your favourite vitamin? (A-K) ",
"\e[2K\e[1GYour favourite vitamin? (A-K) E",
"\e[1A\e[2K\e[1G",
"Your favourite vitamin? (A-K) \e[32mE\e[0m\n"
].join)
end
it "provides default error message when wrong input" do
prompt.input << "A\n2\n"
prompt.input.rewind
answer = prompt.ask("How spicy on scale? (1-5)", in: '1-5')
expect(answer).to eq('2')
expect(prompt.output.string).to eq([
"How spicy on scale? (1-5) ",
"\e[2K\e[1GHow spicy on scale? (1-5) A",
"\e[2K\e[1GHow spicy on scale? (1-5) A\n",
"\e[31m>>\e[0m Value A must be within the range 1..5\e[1A",
"\e[2K\e[1G",
"How spicy on scale? (1-5) ",
"\e[2K\e[1GHow spicy on scale? (1-5) 2",
"\e[2K\e[1GHow spicy on scale? (1-5) 2\n",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"How spicy on scale? (1-5) \e[32m2\e[0m\n"
].join)
end
it "overwrites default error message when wrong input" do
prompt.input << "A\n2\n"
prompt.input.rewind
answer = prompt.ask("How spicy on scale? (1-5)") do |q|
q.in '1-5'
q.messages[:range?] = 'Ohh dear what is this %{value} doing in %{in}?'
end
expect(answer).to eq('2')
expect(prompt.output.string).to eq([
"How spicy on scale? (1-5) ",
"\e[2K\e[1GHow spicy on scale? (1-5) A",
"\e[2K\e[1GHow spicy on scale? (1-5) A\n",
"\e[31m>>\e[0m Ohh dear what is this A doing in 1..5?\e[1A",
"\e[2K\e[1G",
"How spicy on scale? (1-5) ",
"\e[2K\e[1GHow spicy on scale? (1-5) 2",
"\e[2K\e[1GHow spicy on scale? (1-5) 2\n",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"How spicy on scale? (1-5) \e[32m2\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/question/initialize_spec.rb 0000664 0000000 0000000 00000000513 13631250432 0023207 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#initialize' do
subject(:question) { described_class.new(TTY::TestPrompt.new)}
it { expect(question.echo).to eq(true) }
it { expect(question.modifier).to eq([]) }
it { expect(question.validation).to eq(TTY::Prompt::Question::UndefinedSetting) }
end
tty-prompt-0.21.0/spec/unit/question/modifier/ 0000775 0000000 0000000 00000000000 13631250432 0021306 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/question/modifier/apply_to_spec.rb 0000664 0000000 0000000 00000001365 13631250432 0024501 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question::Modifier, '#apply_to' do
let(:string) { "text to be modified"}
it "doesn't apply modifiers" do
modifier = described_class.new([])
expect(modifier.apply_to(string)).to eq(string)
end
it 'combines whitespace & letter case modifications' do
modifiers = [:collapse, :capitalize]
modifier = described_class.new(modifiers)
modified = modifier.apply_to(string)
expect(modified).to eq('Text to be modified')
end
it 'combines letter case & whitespace modifications' do
modifiers = [:up, :collapse]
modifier = described_class.new(modifiers)
modified = modifier.apply_to(string)
expect(modified).to eq('TEXT TO BE MODIFIED')
end
end
tty-prompt-0.21.0/spec/unit/question/modifier/letter_case_spec.rb 0000664 0000000 0000000 00000002115 13631250432 0025136 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question::Modifier, '#letter_case' do
context "string" do
let(:string) { 'text to modify' }
it "changes to uppercase" do
modified = described_class.letter_case(:up, string)
expect(modified).to eq('TEXT TO MODIFY')
end
it "changes to lower case" do
modified = described_class.letter_case(:down, string)
expect(modified).to eq('text to modify')
end
it "capitalizes text" do
modified = described_class.letter_case(:capitalize, string)
expect(modified).to eq('Text to modify')
end
end
context "nil (empty user input)" do
let(:string) { nil }
example "up returns nil" do
modified = described_class.letter_case(:up, string)
expect(modified).to be_nil
end
example "down returns nil" do
modified = described_class.letter_case(:down, string)
expect(modified).to be_nil
end
example "capitalize returns nil" do
modified = described_class.letter_case(:capitalize, string)
expect(modified).to be_nil
end
end
end
tty-prompt-0.21.0/spec/unit/question/modifier/whitespace_spec.rb 0000664 0000000 0000000 00000002625 13631250432 0025006 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question::Modifier, '#whitespace' do
context "string with whitespaces" do
let(:string) { " text\t \n to\t modify\r\n" }
it "trims whitespace" do
modified = described_class.whitespace(:trim, string)
expect(modified).to eq("text\t \n to\t modify")
end
it "chomps whitespace" do
modified = described_class.whitespace(:chomp, string)
expect(modified).to eq(" text\t \n to\t modify")
end
it "collapses text" do
modified = described_class.whitespace(:collapse, string)
expect(modified).to eq(" text to modify ")
end
it "removes whitespace" do
modified = described_class.whitespace(:remove, string)
expect(modified).to eq("texttomodify")
end
end
context "nil (empty user input)" do
let(:string) { nil }
example "trim returns nil" do
modified = described_class.whitespace(:trim, string)
expect(modified).to be_nil
end
example "chomp returns nil" do
modified = described_class.whitespace(:chomp, string)
expect(modified).to be_nil
end
example "collapse returns nil" do
modified = described_class.whitespace(:collapse, string)
expect(modified).to be_nil
end
example "remove returns nil" do
modified = described_class.whitespace(:remove, string)
expect(modified).to be_nil
end
end
end
tty-prompt-0.21.0/spec/unit/question/modify_spec.rb 0000664 0000000 0000000 00000002432 13631250432 0022337 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#modify' do
subject(:prompt) { TTY::TestPrompt.new }
it 'preserves answer for unkown modification' do
prompt.input << 'piotr'
prompt.input.rewind
answer = prompt.ask("What is your name?") { |q| q.modify(:none) }
expect(answer).to eq('piotr')
end
it 'converts to upper case' do
prompt.input << 'piotr'
prompt.input.rewind
answer = prompt.ask("What is your name?") { |q| q.modify(:upcase) }
expect(answer).to eq('PIOTR')
end
it 'trims whitespace' do
prompt.input << " Some white\t space\t \there! \n"
prompt.input.rewind
answer = prompt.ask('Enter some text: ') { |q| q.modify(:trim) }
expect(answer).to eq("Some white\t space\t \there!")
end
it 'collapses whitespace' do
prompt.input << " Some white\t space\t \there! \n"
prompt.input.rewind
answer = prompt.ask('Enter some text: ') { |q| q.modify(:collapse) }
expect(answer).to eq(' Some white space here! ')
end
it 'strips and collapses whitespace' do
prompt.input << " Some white\t space\t \there! \n"
prompt.input.rewind
answer = prompt.ask('Enter some text: ') { |q| q.modify(:strip, :collapse) }
expect(answer).to eq('Some white space here!')
end
end
tty-prompt-0.21.0/spec/unit/question/required_spec.rb 0000664 0000000 0000000 00000005533 13631250432 0022675 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#required' do
subject(:prompt) { TTY::TestPrompt.new }
it 'requires value to be present' do
prompt.input << "Piotr"
prompt.input.rewind
prompt.ask('What is your name?') { |q| q.required(true) }
expect(prompt.output.string).to eq([
"What is your name? ",
"\e[2K\e[1GWhat is your name? P",
"\e[2K\e[1GWhat is your name? Pi",
"\e[2K\e[1GWhat is your name? Pio",
"\e[2K\e[1GWhat is your name? Piot",
"\e[2K\e[1GWhat is your name? Piotr",
"\e[1A\e[2K\e[1G",
"What is your name? \e[32mPiotr\e[0m\n"
].join)
end
it 'requires value to be present with option' do
prompt.input << " \nPiotr"
prompt.input.rewind
prompt.ask('What is your name?', required: true)
expect(prompt.output.string).to eq([
"What is your name? ",
"\e[2K\e[1GWhat is your name? ",
"\e[2K\e[1GWhat is your name? ",
"\e[2K\e[1GWhat is your name? \n",
"\e[31m>>\e[0m Value must be provided\e[1A",
"\e[2K\e[1G",
"What is your name? ",
"\e[2K\e[1GWhat is your name? P",
"\e[2K\e[1GWhat is your name? Pi",
"\e[2K\e[1GWhat is your name? Pio",
"\e[2K\e[1GWhat is your name? Piot",
"\e[2K\e[1GWhat is your name? Piotr",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"What is your name? \e[32mPiotr\e[0m\n"
].join)
end
it "doesn't require value to be present" do
prompt.input << ''
prompt.input.rewind
answer = prompt.ask('What is your name?') { |q| q.required(false) }
expect(answer).to be_nil
end
it "uses required in validation check" do
prompt.input << " \nexists\ntest\n"
prompt.input.rewind
answer = prompt.ask('File name?') do |q|
q.required(true)
q.validate { |v| !(v =~ /exists/) }
q.messages[:required?] = 'File name must not be empty!'
q.messages[:valid?] = 'File already exists!'
end
expect(answer).to eq('test')
expect(prompt.output.string).to eq([
"File name? ",
"\e[2K\e[1GFile name? ",
"\e[2K\e[1GFile name? ",
"\e[2K\e[1GFile name? \n",
"\e[31m>>\e[0m File name must not be empty!",
"\e[1A\e[2K\e[1G",
"File name? ",
"\e[2K\e[1GFile name? e",
"\e[2K\e[1GFile name? ex",
"\e[2K\e[1GFile name? exi",
"\e[2K\e[1GFile name? exis",
"\e[2K\e[1GFile name? exist",
"\e[2K\e[1GFile name? exists",
"\e[2K\e[1GFile name? exists\n",
"\e[31m>>\e[0m File already exists!",
"\e[1A\e[2K\e[1G",
"File name? ",
"\e[2K\e[1GFile name? t",
"\e[2K\e[1GFile name? te",
"\e[2K\e[1GFile name? tes",
"\e[2K\e[1GFile name? test",
"\e[2K\e[1GFile name? test\n",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"File name? \e[32mtest\e[0m\n",
].join)
expect(answer).to eq('test')
end
end
tty-prompt-0.21.0/spec/unit/question/validate_spec.rb 0000664 0000000 0000000 00000006625 13631250432 0022651 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question, '#validate' do
subject(:prompt) { TTY::TestPrompt.new }
it 'validates input with regex' do
prompt.input << 'p.m'
prompt.input.rewind
answer = prompt.ask('What is your username?') do |q|
q.validate(/^[^\.]+\.[^\.]+/)
end
expect(answer).to eq('p.m')
expect(prompt.output.string).to eq([
"What is your username? ",
"\e[2K\e[1GWhat is your username? p",
"\e[2K\e[1GWhat is your username? p.",
"\e[2K\e[1GWhat is your username? p.m",
"\e[1A\e[2K\e[1G",
"What is your username? \e[32mp.m\e[0m\n"
].join)
end
it 'validates input with proc' do
prompt.input << 'piotr.murach'
prompt.input.rewind
answer = prompt.ask('What is your username?') do |q|
q.validate { |input| input =~ /^[^\.]+\.[^\.]+/ }
end
expect(answer).to eq('piotr.murach')
end
it 'understands custom validation like :email' do
prompt.input << 'piotr@example.com'
prompt.input.rewind
answer = prompt.ask('What is your email?') do |q|
q.validate :email
end
expect(answer).to eq('piotr@example.com')
end
it "provides default error message for wrong input" do
prompt.input << "wrong\np@m.com\n"
prompt.input.rewind
answer = prompt.ask('What is your email?') do |q|
q.validate :email
end
expect(answer).to eq('p@m.com')
expect(prompt.output.string).to eq([
"What is your email? ",
"\e[2K\e[1GWhat is your email? w",
"\e[2K\e[1GWhat is your email? wr",
"\e[2K\e[1GWhat is your email? wro",
"\e[2K\e[1GWhat is your email? wron",
"\e[2K\e[1GWhat is your email? wrong",
"\e[2K\e[1GWhat is your email? wrong\n",
"\e[31m>>\e[0m Your answer is invalid (must match :email)\e[1A",
"\e[2K\e[1G",
"What is your email? ",
"\e[2K\e[1GWhat is your email? p",
"\e[2K\e[1GWhat is your email? p@",
"\e[2K\e[1GWhat is your email? p@m",
"\e[2K\e[1GWhat is your email? p@m.",
"\e[2K\e[1GWhat is your email? p@m.c",
"\e[2K\e[1GWhat is your email? p@m.co",
"\e[2K\e[1GWhat is your email? p@m.com",
"\e[2K\e[1GWhat is your email? p@m.com\n",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"What is your email? \e[32mp@m.com\e[0m\n"
].join)
end
it "provides custom error message for wrong input" do
prompt.input << "wrong\np@m.com"
prompt.input.rewind
answer = prompt.ask('What is your email?') do |q|
q.validate :email
q.messages[:valid?] = 'Not an email!'
end
expect(answer).to eq('p@m.com')
expect(prompt.output.string).to eq([
"What is your email? ",
"\e[2K\e[1GWhat is your email? w",
"\e[2K\e[1GWhat is your email? wr",
"\e[2K\e[1GWhat is your email? wro",
"\e[2K\e[1GWhat is your email? wron",
"\e[2K\e[1GWhat is your email? wrong",
"\e[2K\e[1GWhat is your email? wrong\n",
"\e[31m>>\e[0m Not an email!\e[1A",
"\e[2K\e[1G",
"What is your email? ",
"\e[2K\e[1GWhat is your email? p",
"\e[2K\e[1GWhat is your email? p@",
"\e[2K\e[1GWhat is your email? p@m",
"\e[2K\e[1GWhat is your email? p@m.",
"\e[2K\e[1GWhat is your email? p@m.c",
"\e[2K\e[1GWhat is your email? p@m.co",
"\e[2K\e[1GWhat is your email? p@m.com",
"\e[2K\e[1G",
"\e[1A\e[2K\e[1G",
"What is your email? \e[32mp@m.com\e[0m\n"
].join)
end
end
tty-prompt-0.21.0/spec/unit/question/validation/ 0000775 0000000 0000000 00000000000 13631250432 0021642 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/question/validation/call_spec.rb 0000664 0000000 0000000 00000001636 13631250432 0024122 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question::Validation, '#call' do
let(:pattern) { /^[^\.]+\.[^\.]+/ }
it "validates nil input" do
validation = described_class.new(pattern)
expect(validation.(nil)).to eq(false)
end
it "validates successfully when the value matches pattern" do
validation = described_class.new(pattern)
expect(validation.('piotr.murach')).to eq(true)
end
it "validates with a proc" do
pat = proc { |input| !pattern.match(input).nil? }
validation = described_class.new(pat)
expect(validation.call('piotr.murach')).to eq(true)
end
it "validates with custom name" do
validation = described_class.new(:email)
expect(validation.call('piotr@example.com')).to eq(true)
end
it "fails validation when not maching pattern" do
validation = described_class.new(pattern)
expect(validation.('piotrmurach')).to eq(false)
end
end
tty-prompt-0.21.0/spec/unit/question/validation/coerce_spec.rb 0000664 0000000 0000000 00000001453 13631250432 0024444 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Question::Validation, '#coerce' do
let(:instance) { described_class.new }
it "coerces lambda into proc" do
pattern = lambda { "^[^\.]+\.[^\.]+" }
validation = described_class.new(pattern)
expect(validation.pattern).to be_a(Proc)
end
it "doesn't coerce symbols" do
pattern = :email
validation =described_class.new(pattern)
expect(validation.pattern).to eq(:email)
end
it "coerces into regex" do
pattern = /^[^\.]+\.[^\.]+/
validation = described_class.new(pattern)
expect(validation.pattern).to be_a(Regexp)
end
it "fails to coerce pattern into validation" do
pattern = Object.new
expect {
described_class.new(pattern)
}.to raise_error(TTY::Prompt::ValidationCoercion)
end
end
tty-prompt-0.21.0/spec/unit/result_spec.rb 0000664 0000000 0000000 00000002066 13631250432 0020522 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Result do
it "checks value to be invalid" do
question = double(:question)
result = TTY::Prompt::Result.new(question, nil)
answer = result.with { |quest, value|
if value.nil?
[value, ["`#{value}` provided cannot be empty"]]
else
value
end
}
expect(answer).to be_a(TTY::Prompt::Result::Failure)
expect(answer.success?).to eq(false)
expect(answer.errors).to eq(["`` provided cannot be empty"])
end
it "checks value to be valid" do
question = double(:question)
result = TTY::Prompt::Result.new(question, 'Piotr')
CheckRequired = Class.new do
def self.call(quest, value)
if value.nil?
[value, ["`#{value}` provided cannot be empty"]]
else
value
end
end
end
answer = result.with(CheckRequired)
expect(answer).to be_a(TTY::Prompt::Result::Success)
expect(answer.success?).to eq(true)
expect(answer.value).to eq('Piotr')
expect(answer.errors).to eq([])
end
end
tty-prompt-0.21.0/spec/unit/say_spec.rb 0000664 0000000 0000000 00000003423 13631250432 0017776 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#say' do
subject(:prompt) { TTY::TestPrompt.new }
it 'prints an empty message' do
prompt.say('')
expect(prompt.output.string).to eq('')
end
context 'with new line' do
it 'prints a message with newline' do
prompt.say("Hell yeah!\n")
expect(prompt.output.string).to eq("Hell yeah!\n")
end
it 'prints a message with implicit newline' do
prompt.say("Hell yeah!\n")
expect(prompt.output.string).to eq("Hell yeah!\n")
end
it 'prints a message with newline within text' do
prompt.say("Hell\n yeah!")
expect(prompt.output.string).to eq("Hell\n yeah!\n")
end
it 'prints a message with newline within text and blank space' do
prompt.say("Hell\n yeah! ")
expect(prompt.output.string).to eq("Hell\n yeah! ")
end
it 'prints a message without newline' do
prompt.say("Hell yeah!", newline: false)
expect(prompt.output.string).to eq("Hell yeah!")
end
end
context 'with tab or space' do
it 'prints ' do
prompt.say("Hell yeah!\t")
expect(prompt.output.string).to eq("Hell yeah!\t")
end
end
context 'with color' do
it 'prints message with ansi color' do
prompt.say('Hell yeah!', color: :green)
expect(prompt.output.string).to eq("\e[32mHell yeah!\e[0m\n")
end
it 'prints message with ansi color without newline' do
prompt.say('Hell yeah! ', color: :green)
expect(prompt.output.string).to eq("\e[32mHell yeah! \e[0m")
end
end
context 'without color' do
it 'prints message without ansi' do
prompt = TTY::TestPrompt.new(enable_color: false)
prompt.say('Hell yeah!', color: :green)
expect(prompt.output.string).to eq("Hell yeah!\n")
end
end
end
tty-prompt-0.21.0/spec/unit/select_spec.rb 0000664 0000000 0000000 00000100577 13631250432 0020471 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#select' do
subject(:prompt) { TTY::TestPrompt.new }
let(:symbols) { TTY::Prompt::Symbols.symbols }
let(:up_down) { "#{symbols[:arrow_up]}/#{symbols[:arrow_down]}" }
let(:left_right) { "#{symbols[:arrow_left]}/#{symbols[:arrow_right]}"}
def output_helper(prompt, choices, active, options = {})
raise ":init requires :hint" if options[:init] && options[:hint].nil?
hint = options[:hint]
init = options.fetch(:init, false)
enum = options[:enum]
out = []
out << "\e[?25l" if init
out << prompt << " "
out << "\e[90m(#{hint})\e[0m" if hint
out << "\n"
out << choices.map.with_index do |c, i|
name = c.is_a?(Hash) ? c[:name] : c
disabled = c.is_a?(Hash) ? c[:disabled] : false
num = (i + 1).to_s + enum if enum
if disabled
"\e[31m#{symbols[:cross]}\e[0m #{num}#{name} #{disabled}"
elsif name == active
"\e[32m#{symbols[:marker]} #{num}#{name}\e[0m"
else
" #{num}#{name}"
end
end.join("\n")
out << "\e[2K\e[1G\e[1A" * choices.count
out << "\e[2K\e[1G"
out << "\e[1A\e[2K\e[1G" if choices.empty?
out.join
end
def exit_message(prompt, choice)
"#{prompt} \e[32m#{choice}\e[0m\n\e[?25h"
end
# Ensure a wide prompt on CI
before { allow(TTY::Screen).to receive(:width).and_return(200) }
it "selects by default first option" do
choices = %i(Large Medium Small)
prompt.input << "\r"
prompt.input.rewind
expect(prompt.select('What size?', choices)).to eq(:Large)
expected_output = [
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mLarge\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "allows navigation using events without errors" do
choices = %w(Large Medium Small)
prompt.input << "j" << "\r"
prompt.input.rewind
prompt.on(:keypress) do |event|
prompt.trigger(:keydown) if event.value == "j"
end
expect { prompt.select('What size?', choices) }.not_to output.to_stderr
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \n",
" Large\n",
"\e[32m#{symbols[:marker]} Medium\e[0m\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mMedium\e[0m\n\e[?25h"
].join)
end
it "sets choice name and value" do
choices = {large: 1, medium: 2, small: 3}
prompt.input << " "
prompt.input.rewind
expect(prompt.select('What size?', choices, default: 1)).to eq(1)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} large\e[0m\n",
" medium\n",
" small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mlarge\e[0m\n\e[?25h"
].join)
end
it "sets choice name through DSL" do
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?') do |menu|
menu.symbols marker: '>'
menu.choice "Large"
menu.choice "Medium"
menu.choice "Small"
end
expect(value).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m> Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mLarge\e[0m\n\e[?25h"
].join)
end
it "sets choice name & value through DSL" do
prompt = TTY::TestPrompt.new(symbols: {marker: '>'})
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?') do |menu|
menu.choice :large, 1
menu.choice :medium, 2
menu.choice :small, 3
end
expect(value).to eq(1)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m> large\e[0m\n",
" medium\n",
" small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mlarge\e[0m\n\e[?25h"
].join)
end
it "sets choices and single choice through DSL" do
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?') do |menu|
menu.choice 'Large'
menu.choices %w(Medium Small)
end
expect(value).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mLarge\e[0m\n\e[?25h"
].join)
end
it "sets choice name & value through DSL" do
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?') do |menu|
menu.default 2
menu.enum '.'
menu.choice :large, 1
menu.choice :medium, 2
menu.choice :small, 3
end
expect(value).to eq(2)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow or number (1-3) keys, press Enter to select)\e[0m\n",
" 1. large\n",
"\e[32m#{symbols[:marker]} 2. medium\e[0m\n",
" 3. small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mmedium\e[0m\n\e[?25h"
].join)
end
it "sets choice value to proc and executes it" do
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?', default: 2, enum: ')') do |menu|
menu.choice :large, 1
menu.choice :medium do 'Good choice!' end
menu.choice :small, 3
end
expect(value).to eq('Good choice!')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow or number (1-3) keys, press Enter to select)\e[0m\n",
" 1) large\n",
"\e[32m#{symbols[:marker]} 2) medium\e[0m\n",
" 3) small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mmedium\e[0m\n\e[?25h"
].join)
end
it "sets default option through hash syntax" do
choices = %w(Large Medium Small)
prompt.input << " "
prompt.input.rewind
expect(prompt.select('What size?', choices, default: 2, enum: '.')).to eq('Medium')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Use #{up_down} arrow or number (1-3) keys, press Enter to select)\e[0m\n",
" 1. Large\n",
"\e[32m#{symbols[:marker]} 2. Medium\e[0m\n",
" 3. Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mMedium\e[0m\n\e[?25h"
].join)
end
it "changes selected item color & marker" do
choices = %w(Large Medium Small)
prompt = TTY::TestPrompt.new(symbols: {marker: '>'})
prompt.input << " "
prompt.input.rewind
options = {active_color: :blue, help_color: :red, symbols: {marker: '>' }}
value = prompt.select('What size?', choices, **options)
expect(value).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[31m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[34m> Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[34mLarge\e[0m\n\e[?25h"
].join)
end
it "changes help text" do
choices = %w(Large Medium Small)
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?', choices, help: "(Bash keyboard)")
expect(value).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Bash keyboard)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mLarge\e[0m\n\e[?25h"
].join)
end
it "changes help text through DSL" do
choices = %w(Large Medium Small)
prompt.input << " "
prompt.input.rewind
value = prompt.select('What size?') do |menu|
menu.help "(Bash keyboard)"
menu.choices choices
end
expect(value).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25lWhat size? \e[90m(Bash keyboard)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What size? \e[32mLarge\e[0m\n\e[?25h"
].join)
end
it "sets prompt prefix" do
prompt = TTY::TestPrompt.new(prefix: '[?] ')
choices = %w(Large Medium Small)
prompt.input << "\r"
prompt.input.rewind
expect(prompt.select('What size?', choices)).to eq('Large')
expect(prompt.output.string).to eq([
"\e[?25l[?] What size? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} Large\e[0m\n",
" Medium\n",
" Small",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"[?] What size? \e[32mLarge\e[0m\n\e[?25h"
].join)
end
context "when paginated" do
it "paginates long selections" do
choices = %w(A B C D E F G H)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.select("What letter?", choices, per_page: 3, default: 4)
expect(answer).to eq('D')
expected_output = [
"\e[?25lWhat letter? \e[90m(Use #{up_down} and #{left_right} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} D\e[0m\n",
" E\n",
" F",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "paginates choices as hash object" do
prompt = TTY::TestPrompt.new
choices = {A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8}
prompt.input << "\r"
prompt.input.rewind
answer = prompt.select("What letter?", choices, per_page: 3, default: 4)
expect(answer).to eq(4)
expected_output = [
"\e[?25lWhat letter? \e[90m(Use #{up_down} and #{left_right} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} D\e[0m\n",
" E\n",
" F",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "paginates long selections through DSL" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D E F G H)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.select("What letter?") do |menu|
menu.per_page 3
menu.default 4
menu.choices choices
end
expect(answer).to eq('D')
expected_output = [
"\e[?25lWhat letter? \e[90m(Use #{up_down} and #{left_right} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} D\e[0m\n",
" E\n",
" F",
"\e[2K\e[1G\e[1A" * 3,
"\e[2K\e[1G",
"What letter? \e[32mD\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates evenly paged output with right arrow until end of selection" do
prompt = TTY::TestPrompt.new
choices = ('1'..'12').to_a
prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
prompt.input << "l" << "l" << "l" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, per_page: 4)
expect(answer).to eq('9')
expected_output = [
output_helper('What number?', choices[0..3], "1", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "5"),
output_helper('What number?', choices[8..11], "9"),
output_helper('What number?', choices[8..11], "9"),
"What number? \e[32m9\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates unevenly paged output with right arrow until the end of selection" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
prompt.input << "l" << "l" << "l" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, default: 4, per_page: 4)
expect(answer).to eq('10')
expected_output = [
output_helper('What number?', choices[3..6], "4", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "8"),
output_helper('What number?', choices[8..9], "10"),
output_helper('What number?', choices[8..9], "10"),
"What number? \e[32m10\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates left and right" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "l" << "l" << "h" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, default: 2, per_page: 4)
expect(answer).to eq('6')
expected_output = [
output_helper('What number?', choices[0..3], "2", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "6"),
output_helper('What number?', choices[8..9], "10"),
output_helper('What number?', choices[4..7], "6"),
"What number? \e[32m6\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "combines up/down navigation with left/right" do
prompt = TTY::TestPrompt.new
choices = ('1'..'11').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyup) if e.value == "k"
prompt.trigger(:keydown) if e.value == "j"
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "j" << "l" << "k" << "k" << "h" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, default: 2, per_page: 4)
expect(answer).to eq('1')
expected_output = [
output_helper('What number?', choices[0..3], "2", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[0..3], "3"),
output_helper('What number?', choices[4..7], "7"),
output_helper('What number?', choices[4..7], "6"),
output_helper('What number?', choices[3..6], "5"),
output_helper('What number?', choices[0..3], "1"),
"What number? \e[32m1\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "navigates pages up/down with disabled items" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e|
prompt.trigger(:keyup) if e.value == "k"
prompt.trigger(:keydown) if e.value == "j"
}
choices = [
'1',
{name: '2', disabled: 'out'},
'3',
{name: '4', disabled: 'out'},
'5',
{name: '6', disabled: 'out'},
{name: '7', disabled: 'out'},
'8',
'9',
{name: '10', disabled: 'out'},
]
prompt.input << "j" << "j" << "j" << "j" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, per_page: 4)
expect(answer).to eq('9')
expected_output = [
output_helper('What number?', choices[0..3], "1", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[0..3], "3"),
output_helper('What number?', choices[2..5], "5"),
output_helper('What number?', choices[5..8], "8"),
output_helper('What number?', choices[6..9], "9"),
"What number? \e[32m9\e[0m\n\e[?25h"
].join('')
expect(prompt.output.string).to eq(expected_output)
end
it "navigates pages left/right with disabled items" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
choices = [
{name: '1', disabled: 'out'},
'2',
{name: '3', disabled: 'out'},
'4',
'5',
{name: '6', disabled: 'out'},
'7',
'8',
'9',
{name: '10', disabled: 'out'}
]
prompt.input << "l" << "l" << "l" << "h" << "h" << "h" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, per_page: 4)
expect(answer).to eq('2')
expected_output = [
output_helper('What number?', choices[0..3], "2", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "7"),
output_helper('What number?', choices[8..9], "9"),
output_helper('What number?', choices[8..9], "9"),
output_helper('What number?', choices[4..7], "5"),
output_helper('What number?', choices[0..3], "2"),
output_helper('What number?', choices[0..3], "2"),
"What number? \e[32m2\e[0m\n\e[?25h"
].join('')
expect(prompt.output.string).to eq(expected_output)
end
end
context 'with :cycle option' do
it "doesn't cycle by default" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << "j" << "j" << "\r"
prompt.input.rewind
value = prompt.select("What letter?", choices)
expect(value).to eq("C")
expected_output = [
output_helper("What letter?", choices, "A", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select"),
output_helper("What letter?", choices, "B"),
output_helper("What letter?", choices, "C"),
output_helper("What letter?", choices, "C"),
"What letter? \e[32mC\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "cycles around when configured to do so" do
prompt = TTY::TestPrompt.new
choices = %w(A B C)
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << "j" << "j" << "\r"
prompt.input.rewind
answer = prompt.select("What letter?", choices, cycle: true)
expect(answer).to eq("A")
expected_output = [
output_helper("What letter?", choices, "A", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select"),
output_helper("What letter?", choices, "B"),
output_helper("What letter?", choices, "C"),
output_helper("What letter?", choices, "A"),
"What letter? \e[32mA\e[0m\n\e[?25h"
].join
expect(prompt.output.string).to eq(expected_output)
end
it "cycles around disabled items" do
prompt = TTY::TestPrompt.new
choices = [
{name: 'A', disabled: '(out)'},
{name: 'B'},
{name: 'C', disabled: '(out)'},
{name: 'D'},
{name: 'E', disabled: '(out)'},
]
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
prompt.input << "j" << "j" << "j" << "\r"
prompt.input.rewind
value = prompt.select("What letter?", choices, cycle: true, default: 2)
expect(value).to eq("D")
expected_output =
output_helper("What letter?", choices, "B", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select") +
output_helper("What letter?", choices, "D") +
output_helper("What letter?", choices, "B") +
output_helper("What letter?", choices, "D") +
"What letter? \e[32mD\e[0m\n\e[?25h"
expect(prompt.output.string).to eq(expected_output)
end
it "cycles choices using left/right arrows" do
prompt = TTY::TestPrompt.new
choices = ('1'..'10').to_a
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
prompt.input << "l" << "l" << "l" << "h" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, default: 2, per_page: 4, cycle: true)
expect(answer).to eq('10')
expected_output = [
output_helper('What number?', choices[0..3], "2", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "6"),
output_helper('What number?', choices[8..9], "10"),
output_helper('What number?', choices[0..3], "2"),
output_helper('What number?', choices[8..9], "10"),
"What number? \e[32m10\e[0m\n\e[?25h",
].join
expect(prompt.output.string).to eq(expected_output)
end
it "cycles pages left/right with disabled items" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e|
prompt.trigger(:keyright) if e.value == "l"
prompt.trigger(:keyleft) if e.value == "h"
}
choices = [
{name: '1', disabled: 'out'},
'2',
{name: '3', disabled: 'out'},
'4',
'5',
{name: '6', disabled: 'out'},
'7',
'8',
'9',
{name: '10', disabled: 'out'}
]
prompt.input << "l" << "l" << "l" << "h" << "h" << "h" << "\r"
prompt.input.rewind
answer = prompt.select("What number?", choices, per_page: 4, cycle: true)
expect(answer).to eq('2')
expected_output = [
output_helper('What number?', choices[0..3], "2", init: true,
hint: "Use #{up_down} and #{left_right} arrow keys, press Enter to select"),
output_helper('What number?', choices[4..7], "7"),
output_helper('What number?', choices[8..9], "9"),
output_helper('What number?', choices[0..3], "2"),
output_helper('What number?', choices[8..9], "9"),
output_helper('What number?', choices[4..7], "5"),
output_helper('What number?', choices[0..3], "2"),
"What number? \e[32m2\e[0m\n\e[?25h"
].join('')
expect(prompt.output.string).to eq(expected_output)
end
end
it "verifies default index format" do
prompt = TTY::TestPrompt.new
choices = %w(Large Medium Small)
prompt.input << "\r"
prompt.input.rewind
expect {
prompt.select('What size?', choices, default: '')
}.to raise_error(TTY::Prompt::ConfigurationError, /in range \(1 - 3\)/)
end
it "doesn't paginate short selections" do
prompt = TTY::TestPrompt.new
choices = %w(A B C D)
prompt.input << "\r"
prompt.input.rewind
value = prompt.select("What letter?", choices, per_page: 4, default: 1)
expect(value).to eq('A')
expect(prompt.output.string).to eq([
"\e[?25lWhat letter? \e[90m(Use #{up_down} arrow keys, press Enter to select)\e[0m\n",
"\e[32m#{symbols[:marker]} A\e[0m\n",
" B\n",
" C\n",
" D",
"\e[2K\e[1G\e[1A" * 4,
"\e[2K\e[1G",
"What letter? \e[32mA\e[0m\n\e[?25h",
].join)
end
it "verifies default index range" do
prompt = TTY::TestPrompt.new
choices = %w(Large Medium Small)
prompt.input << "\r"
prompt.input.rewind
expect {
prompt.select("What size?", choices, default: 10)
}.to raise_error(TTY::Prompt::ConfigurationError, /`10` out of range \(1 - 3\)/)
end
context "with filter" do
it "doesn't allow mixing enumeration and filter" do
prompt = TTY::TestPrompt.new
expect {
prompt.select("What size?", [], enum: '.', filter: true)
}.to raise_error(TTY::Prompt::ConfigurationError, "Enumeration can't be used with filter")
end
it "filters and chooses a uniquely matching entry, ignoring case" do
prompt = TTY::TestPrompt.new
prompt.input << "U" << "g" << "\r"
prompt.input.rewind
answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
expect(answer).to eql("Huge")
actual_prompt_output = prompt.output.string
expected_prompt_output =
output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What size?", %w(Medium Huge), "Medium", hint: 'Filter: "U"') +
output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "Ug"') +
exit_message("What size?", "Huge")
expect(actual_prompt_output).to eql(expected_prompt_output)
end
it "filters and chooses the first of multiple matching entries" do
prompt = TTY::TestPrompt.new
prompt.input << "g" << "\r"
prompt.input.rewind
answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
expect(answer).to eql("Large")
actual_prompt_output = prompt.output.string
expected_prompt_output =
output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What size?", %w(Large Huge), "Large", hint: 'Filter: "g"') +
exit_message("What size?", "Large")
expect(actual_prompt_output).to eql(expected_prompt_output)
end
it "filters based on alphanumeric and punctuation characters" do
prompt = TTY::TestPrompt.new
prompt.input << "p" << "*" << "2" << "\r"
prompt.input.rewind
answer = prompt.select("What email?", %w(p*1@mail.com p*2@mail.com p*3@mail.com), filter: true)
expect(answer).to eql("p*2@mail.com")
actual_prompt_output = prompt.output.string
expected_prompt_output =
output_helper("What email?", %w(p*1@mail.com p*2@mail.com p*3@mail.com), "p*1@mail.com", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What email?", %w(p*1@mail.com p*2@mail.com p*3@mail.com), "p*1@mail.com", hint: 'Filter: "p"') +
output_helper("What email?", %w(p*1@mail.com p*2@mail.com p*3@mail.com), "p*1@mail.com", hint: 'Filter: "p*"') +
output_helper("What email?", %w(p*2@mail.com), "p*2@mail.com", hint: 'Filter: "p*2"') +
exit_message("What email?", "p*2@mail.com")
expect(actual_prompt_output).to eql(expected_prompt_output)
end
# This test can't be done in an exact way, at least, with the current framework
it "doesn't exit when there are no matching entries" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keybackspace) if e.value == "a" }
prompt.input << "z" << "\r" # shows no entry, blocking exit
prompt.input << "a" << "\r" # triggers Backspace before `a` (see above)
prompt.input.rewind
answer = prompt.select("What size?", %w(Tiny Medium Large Huge), filter: true)
expect(answer).to eql("Large")
actual_prompt_output = prompt.output.string
expected_prompt_output =
output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What size?", %w(), "", hint: 'Filter: "z"') +
output_helper("What size?", %w(), "", hint: 'Filter: "z"') +
output_helper("What size?", %w(Large), "Large", hint: 'Filter: "a"') +
exit_message("What size?", "Large")
expect(actual_prompt_output).to eql(expected_prompt_output)
end
it "cancels a selection" do
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keydelete) if e.value == "S" }
prompt.input << "Hu"
prompt.input << "S" # triggers Canc before `S` (see above)
prompt.input << "\r"
prompt.input.rewind
answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
expect(answer).to eql("Small")
expected_prompt_output =
output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "H"') +
output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "Hu"') +
output_helper("What size?", %w(Small), "Small", hint: 'Filter: "S"') +
exit_message("What size?", "Small")
expect(prompt.output.string).to eql(expected_prompt_output)
end
end
context 'with :disabled choice' do
it "omits disabled choice when navigating menu" do
choices = [ 'Small', 'Medium', {name: 'Large', disabled: '(out of stock)'}, 'Huge' ]
prompt = TTY::TestPrompt.new
prompt.input << "j" << "j" << "\r"
prompt.input.rewind
prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
answer = prompt.select("What size?", choices)
expect(answer).to eq('Huge')
expected_output =
output_helper("What size?", choices, "Small", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select") +
output_helper("What size?", choices, "Medium") +
output_helper("What size?", choices, "Huge") +
"What size? \e[32mHuge\e[0m\n\e[?25h"
expect(prompt.output.string).to eq(expected_output)
end
it "doesn't show disabled choice when filtering choices" do
choices = [ 'A', 'B', {name: 'C', disabled: '(unavailable)'}, 'D' ]
prompt = TTY::TestPrompt.new
prompt.on(:keypress) { |e| prompt.trigger(:keybackspace) if e.value == "a" }
prompt.input << "c" << "\r" # nothing matches
prompt.input << "a" << "\r" # backtracks & chooses default option
prompt.input.rewind
answer = prompt.select("What letter?", choices, filter: true)
expect(answer).to eq('A')
expected_output =
output_helper("What letter?", choices, "A", init: true,
hint: "Use #{up_down} arrow keys, press Enter to select, and letter keys to filter") +
output_helper("What letter?", [], "", hint: 'Filter: "c"') +
output_helper("What letter?", [], "", hint: 'Filter: "c"') +
output_helper("What letter?", ['A'], "A", hint: 'Filter: "a"') +
exit_message("What letter?", "A")
expect(prompt.output.string).to eq(expected_output)
end
it "omits disabled choice when number key is pressed" do
choices = [ 'Small', {name: 'Medium', disabled: '(out of stock)'}, 'Large' ]
prompt = TTY::TestPrompt.new
prompt.input << "2" << "\r" << "\r"
prompt.input.rewind
answer = prompt.select('What size?') do |menu|
menu.enum ')'
menu.choice 'Small', 1
menu.choice 'Medium', 2, disabled: '(out of stock)'
menu.choice 'Large', 3
end
expect(answer).to eq(1)
expected_output =
output_helper("What size?", choices, "Small", init: true, enum: ') ',
hint: "Use #{up_down} arrow or number (1-3) keys, press Enter to select") +
output_helper("What size?", choices, "Small", enum: ') ') +
"What size? \e[32mSmall\e[0m\n\e[?25h"
expect(prompt.output.string).to eq(expected_output)
end
it "sets active to be first non-disabled choice" do
choices = [
{name: 'Small', disabled: '(out of stock)'}, 'Medium', 'Large', 'Huge'
]
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
answer = prompt.select("What size?", choices)
expect(answer).to eq('Medium')
end
it "prevents setting default to disabled choice" do
choices = [
{name: 'Small', disabled: '(out of stock)'}, 'Medium', 'Large', 'Huge'
]
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
expect {
prompt.select("What size?", choices, default: 1)
}.to raise_error(TTY::Prompt::ConfigurationError, /default index `1` matches disabled choice item/)
end
end
end
tty-prompt-0.21.0/spec/unit/slider_spec.rb 0000664 0000000 0000000 00000010167 13631250432 0020467 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#slider' do
subject(:prompt) { TTY::TestPrompt.new }
let(:symbols) { TTY::Prompt::Symbols.symbols }
it "specifies ranges & step" do
prompt.input << "\r"
prompt.input.rewind
expect(prompt.slider('What size?', min: 32, max: 54, step: 2)).to eq(44)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
symbols[:line] * 6,
"\e[32m#{symbols[:bullet]}\e[0m",
"#{symbols[:line] * 5} 44",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[32m44\e[0m\n\e[?25h"
].join)
end
it "specifies default value" do
prompt.input << "\r"
prompt.input.rewind
expect(prompt.slider('What size?', min: 32, max: 54, step: 2, default: 38)).to eq(38)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
symbols[:line] * 3,
"\e[32m#{symbols[:bullet]}\e[0m",
"#{symbols[:line] * 8} 38",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[32m38\e[0m\n\e[?25h"
].join)
end
it "specifies range through DSL" do
prompt.input << "\r"
prompt.input.rewind
value = prompt.slider('What size?') do |range|
range.default 6
range.min 0
range.max 20
range.step 2
range.format "|:slider| %d%%"
end
expect(value).to eq(6)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
symbols[:pipe] + symbols[:line] * 3,
"\e[32m#{symbols[:bullet]}\e[0m",
"#{symbols[:line] * 7 + symbols[:pipe]} 6%",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[32m6\e[0m\n\e[?25h"
].join)
end
it "changes display colors" do
prompt.input << "\r"
prompt.input.rewind
options = {active_color: :red, help_color: :cyan}
expect(prompt.slider('What size?', options)).to eq(5)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
symbols[:line] * 5,
"\e[31m#{symbols[:bullet]}\e[0m",
"#{symbols[:line] * 5} 5",
"\n\e[36m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[31m5\e[0m\n\e[?25h"
].join)
end
it "doesn't allow values outside of range" do
prompt.input << "l\r"
prompt.input.rewind
prompt.on(:keypress) do |event|
if event.value = 'l'
prompt.trigger(:keyright)
end
end
res = prompt.slider('What size?', min: 0, max: 10, step: 1, default: 10)
expect(res).to eq(10)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
symbols[:line] * 10,
"\e[32m#{symbols[:bullet]}\e[0m 10",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? ",
symbols[:line] * 10,
"\e[32m#{symbols[:bullet]}\e[0m 10",
"\e[2K\e[1G",
"What size? \e[32m10\e[0m\n\e[?25h"
].join)
end
it "changes all display symbols" do
prompt = TTY::TestPrompt.new(symbols: {
bullet: 'x',
line: '_'
})
prompt.input << "\r"
prompt.input.rewind
expect(prompt.slider('What size?', min: 32, max: 54, step: 2)).to eq(44)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
'_' * 6,
"\e[32mx\e[0m",
"#{'_' * 5} 44",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[32m44\e[0m\n\e[?25h"
].join)
end
it "changes all display symbols per instance" do
prompt = TTY::TestPrompt.new
prompt.input << "\r"
prompt.input.rewind
answer = prompt.slider('What size?', min: 32, max: 54, step: 2) do |range|
range.symbols bullet: 'x', line: '_'
end
expect(answer).to eq(44)
expect(prompt.output.string).to eq([
"\e[?25lWhat size? ",
'_' * 6,
"\e[32mx\e[0m",
"#{'_' * 5} 44",
"\n\e[90m(Use arrow keys, press Enter to select)\e[0m",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"What size? \e[32m44\e[0m\n\e[?25h"
].join)
end
end
tty-prompt-0.21.0/spec/unit/statement/ 0000775 0000000 0000000 00000000000 13631250432 0017645 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/spec/unit/statement/initialize_spec.rb 0000664 0000000 0000000 00000000653 13631250432 0023351 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Statement, '#new' do
it "forces newline after the prompt message" do
prompt = TTY::TestPrompt.new
statement = described_class.new(prompt)
expect(statement.newline).to eq(true)
end
it "displays prompt message in color" do
prompt = TTY::TestPrompt.new
statement = described_class.new(prompt)
expect(statement.color).to eq(false)
end
end
tty-prompt-0.21.0/spec/unit/subscribe_spec.rb 0000664 0000000 0000000 00000001132 13631250432 0021156 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#subscribe' do
it "subscribes to key events only for the current prompt" do
prompt = TTY::TestPrompt.new
uuid = '14c3b412-e0c5-4ff5-9cd8-25ec3f18c702'
prompt.input << "3\n#{uuid}\n"
prompt.input.rewind
keys = []
prompt.on(:keypress) do |event|
keys << :enter if event.key.name == :enter
end
letter = prompt.enum_select('Select something', ('A'..'Z').to_a)
id = prompt.ask('Request ID?')
expect(letter).to eq('C')
expect(id).to eq(uuid)
expect(keys).to eq([:enter, :enter])
end
end
tty-prompt-0.21.0/spec/unit/suggest_spec.rb 0000664 0000000 0000000 00000001644 13631250432 0020666 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#suggest' do
let(:possible) { %w(status stage stash commit branch blame) }
subject(:prompt) { TTY::TestPrompt.new }
it 'suggests few matches' do
prompt.suggest('sta', possible)
expect(prompt.output.string).
to eql("Did you mean one of these?\n stage\n stash\n")
end
it 'suggests a single match for one character' do
prompt.suggest('b', possible)
expect(prompt.output.string).to eql("Did you mean this?\n blame\n")
end
it 'suggests a single match for two characters' do
prompt.suggest('co', possible)
expect(prompt.output.string).to eql("Did you mean this?\n commit\n")
end
it 'suggests with different text and indentation' do
prompt.suggest('b', possible, indent: 4, single_text: 'Perhaps you meant?')
expect(prompt.output.string).to eql("Perhaps you meant?\n blame\n")
end
end
tty-prompt-0.21.0/spec/unit/timer_spec.rb 0000664 0000000 0000000 00000001143 13631250432 0020317 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt::Timer do
it "times out loop execution" do
timer = TTY::Prompt::Timer.new(0.03, 0.01)
yielded = []
timer.while_remaining do |remaining|
expect(remaining).to be_within(0.1).of(timer.duration - yielded.size * 0.01)
yielded << remaining
sleep(0.01)
end
end
it "registers a tick event" do
timer = TTY::Prompt::Timer.new(0.03, 0.01)
yielded = []
timer.on_tick do |time|
yielded << time
end
timer.while_remaining do
# busy work
end
expect(yielded.size).to be >= 2
end
end
tty-prompt-0.21.0/spec/unit/warn_spec.rb 0000664 0000000 0000000 00000001220 13631250432 0020142 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, '#warn' do
subject(:prompt) { TTY::TestPrompt.new }
it 'displays one message' do
prompt.warn "Careful young apprentice!"
expect(prompt.output.string).to eql "\e[33mCareful young apprentice!\e[0m\n"
end
it 'displays many messages' do
prompt.warn "Careful there!", "It's dangerous!"
expect(prompt.output.string).to eql "\e[33mCareful there!\e[0m\n\e[33mIt's dangerous!\e[0m\n"
end
it 'displays message with option' do
prompt.warn "Careful young apprentice!", newline: false
expect(prompt.output.string).to eql "\e[33mCareful young apprentice!\e[0m"
end
end
tty-prompt-0.21.0/spec/unit/yes_no_spec.rb 0000664 0000000 0000000 00000024666 13631250432 0020512 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
RSpec.describe TTY::Prompt, 'confirmation' do
subject(:prompt) { TTY::TestPrompt.new }
context '#yes?' do
it 'agrees with question' do
prompt.input << 'yes'
prompt.input.rewind
expect(prompt.yes?("Are you a human?")).to eq(true)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m y",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m ye",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m yes",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mYes\e[0m\n"
].join)
end
it 'disagrees with question' do
prompt.input << 'no'
prompt.input.rewind
expect(prompt.yes?("Are you a human?")).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m n",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m no",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mno\e[0m\n"
].join)
end
it 'warns about invalid entry when using defaults' do
prompt.input << "test"
prompt.input.rewind
prompt.yes?("Are you a human?")
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m t",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m te",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m tes",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m test",
"\e[31m>>\e[0m Invalid input.\e[1A",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Are you a human? \e[32mYes\e[0m\n"
].join)
end
it 'assumes default true' do
prompt.input << "\r"
prompt.input.rewind
expect(prompt.yes?("Are you a human?")).to eq(true)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mYes\e[0m\n"
].join)
end
it 'changes default' do
prompt.input << "\n"
prompt.input.rewind
expect(prompt.yes?("Are you a human?", default: false)).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mNo\e[0m\n"
].join)
end
it "defaults suffix and converter" do
prompt.input << "Nope\n"
prompt.input.rewind
result = prompt.yes?("Are you a human?") do |q|
q.positive 'Yup'
q.negative 'nope'
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Yup/nope)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m N",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m No",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nop",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nope",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nope\n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mnope\e[0m\n"
].join)
end
it "defaults positive and negative" do
prompt.input << "Nope\n"
prompt.input.rewind
result = prompt.yes?("Are you a human?") do |q|
q.suffix 'Yup/nope'
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Yup/nope)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m N",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m No",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nop",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nope",
"\e[2K\e[1GAre you a human? \e[90m(Yup/nope)\e[0m Nope\n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mnope\e[0m\n"
].join)
end
it "accepts regex conflicting characters as suffix" do
prompt.input << "]\n"
prompt.input.rewind
result = prompt.yes?("Are you a human? [ as yes and ] as no") do |q|
q.suffix "[/]"
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? [ as yes and ] as no \e[90m([/])\e[0m ",
"\e[2K\e[1GAre you a human? [ as yes and ] as no \e[90m([/])\e[0m ]",
"\e[2K\e[1GAre you a human? [ as yes and ] as no \e[90m([/])\e[0m ]\n",
"\e[1A\e[2K\e[1G",
"Are you a human? [ as yes and ] as no \e[32m]\e[0m\n"
].join)
end
it "customizes question through options" do
prompt.input << "\r"
prompt.input.rewind
result = prompt.yes?("Are you a human?", suffix: 'Agree/Disagree',
positive: 'Agree', negative: 'Disagree')
expect(result).to eq(true)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Agree/Disagree)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mAgree\e[0m\n"
].join)
end
it "customizes question through DSL" do
prompt.input << "disagree\r"
prompt.input.rewind
conversion = proc { |input| !input.match(/^agree$/i).nil? }
result = prompt.yes?("Are you a human?") do |q|
q.suffix 'Agree/Disagree'
q.positive 'Agree'
q.negative 'Disagree'
q.convert conversion
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Agree/Disagree)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m d",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m di",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m dis",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disa",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disag",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disagr",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disagre",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disagree",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m disagree\n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mDisagree\e[0m\n"
].join)
end
end
context '#no?' do
it 'agrees with question' do
prompt.input << 'no'
prompt.input.rewind
expect(prompt.no?("Are you a human?")).to eq(true)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m n",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m no",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mNo\e[0m\n"
].join)
end
it 'disagrees with question' do
prompt.input << 'yes'
prompt.input.rewind
expect(prompt.no?("Are you a human?")).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m y",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m ye",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m yes",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32myes\e[0m\n"
].join)
end
it 'warns about invalid entry when using defaults' do
prompt.input << "test"
prompt.input.rewind
prompt.no?("Are you a human?")
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m t",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m te",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m tes",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m test",
"\e[31m>>\e[0m Invalid input.\e[1A",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1G\e[1A\e[2K\e[1G",
"Are you a human? \e[32mNo\e[0m\n"
].join)
end
it 'assumes default false' do
prompt.input << "\r"
prompt.input.rewind
expect(prompt.no?("Are you a human?")).to eq(true)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(y/N)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(y/N)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mNo\e[0m\n"
].join)
end
it 'changes default' do
prompt.input << "\r"
prompt.input.rewind
expect(prompt.no?("Are you a human?", default: true)).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Y/n)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Y/n)\e[0m \n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mYes\e[0m\n"
].join)
end
it "defaults suffix and converter" do
prompt.input << "Yup\n"
prompt.input.rewind
result = prompt.no?("Are you a human?") do |q|
q.positive 'yup'
q.negative 'Nope'
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(yup/Nope)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(yup/Nope)\e[0m Y",
"\e[2K\e[1GAre you a human? \e[90m(yup/Nope)\e[0m Yu",
"\e[2K\e[1GAre you a human? \e[90m(yup/Nope)\e[0m Yup",
"\e[2K\e[1GAre you a human? \e[90m(yup/Nope)\e[0m Yup\n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32myup\e[0m\n"
].join)
end
it "customizes question through DSL" do
prompt.input << "agree\r"
prompt.input.rewind
conversion = proc { |input| !input.match(/^agree$/i).nil? }
result = prompt.no?("Are you a human?") do |q|
q.suffix 'Agree/Disagree'
q.positive 'Agree'
q.negative 'Disagree'
q.convert conversion
end
expect(result).to eq(false)
expect(prompt.output.string).to eq([
"Are you a human? \e[90m(Agree/Disagree)\e[0m ",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m a",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m ag",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m agr",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m agre",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m agree",
"\e[2K\e[1GAre you a human? \e[90m(Agree/Disagree)\e[0m agree\n",
"\e[1A\e[2K\e[1G",
"Are you a human? \e[32mAgree\e[0m\n"
].join)
end
end
end
tty-prompt-0.21.0/tasks/ 0000775 0000000 0000000 00000000000 13631250432 0015055 5 ustar 00root root 0000000 0000000 tty-prompt-0.21.0/tasks/console.rake 0000664 0000000 0000000 00000000333 13631250432 0017362 0 ustar 00root root 0000000 0000000 # encoding: utf-8
desc 'Load gem inside irb console'
task :console do
require 'irb'
require 'irb/completion'
require File.join(__FILE__, '../../lib/tty-prompt')
ARGV.clear
IRB.start
end
task c: %w[ console ]
tty-prompt-0.21.0/tasks/coverage.rake 0000664 0000000 0000000 00000000322 13631250432 0017511 0 ustar 00root root 0000000 0000000 # encoding: utf-8
desc 'Measure code coverage'
task :coverage do
begin
original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
Rake::Task['spec'].invoke
ensure
ENV['COVERAGE'] = original
end
end
tty-prompt-0.21.0/tasks/spec.rake 0000664 0000000 0000000 00000001255 13631250432 0016656 0 ustar 00root root 0000000 0000000 # encoding: utf-8
begin
require 'rspec/core/rake_task'
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec) do |task|
task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb'
end
namespace :spec do
desc 'Run unit specs'
RSpec::Core::RakeTask.new(:unit) do |task|
task.pattern = 'spec/unit{,/*/**}/*_spec.rb'
end
desc 'Run integration specs'
RSpec::Core::RakeTask.new(:integration) do |task|
task.pattern = 'spec/integration{,/*/**}/*_spec.rb'
end
end
rescue LoadError
%w[spec spec:unit spec:integration].each do |name|
task name do
$stderr.puts "In order to run #{name}, do `gem install rspec`"
end
end
end
tty-prompt-0.21.0/tty-prompt.gemspec 0000664 0000000 0000000 00000002704 13631250432 0017437 0 ustar 00root root 0000000 0000000 require_relative "lib/tty/prompt/version"
Gem::Specification.new do |spec|
spec.name = "tty-prompt"
spec.version = TTY::Prompt::VERSION
spec.authors = ["Piotr Murach"]
spec.email = ["piotr@piotrmurach.com"]
spec.summary = %q{A beautiful and powerful interactive command line prompt.}
spec.description = %q{A beautiful and powerful interactive command line prompt with a robust API for getting and validating complex inputs.}
spec.homepage = "https://ttytoolkit.org"
spec.license = "MIT"
if spec.respond_to?(:metadata=)
spec.metadata = {
"allowed_push_host" => "https://rubygems.org",
"bug_tracker_uri" => "https://github.com/piotrmurach/tty-prompt/issues",
"changelog_uri" => "https://github.com/piotrmurach/tty-prompt/blob/master/CHANGELOG.md",
"documentation_uri" => "https://www.rubydoc.info/gems/tty-prompt",
"homepage_uri" => spec.homepage,
"source_code_uri" => "https://github.com/piotrmurach/tty-prompt"
}
end
spec.files = Dir["lib/**/*"]
spec.extra_rdoc_files = Dir["README.md", "CHANGELOG.md", "LICENSE.txt"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.0.0"
spec.add_dependency "necromancer", "~> 0.5.0"
spec.add_dependency "pastel", "~> 0.7.0"
spec.add_dependency "tty-reader", "~> 0.7.0"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec", "~> 3.0"
end