tty2-prompt 0.23.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +23 -0
- data/README.md +52 -0
- data/lib/tty2/prompt/answers_collector.rb +78 -0
- data/lib/tty2/prompt/block_paginator.rb +59 -0
- data/lib/tty2/prompt/choice.rb +147 -0
- data/lib/tty2/prompt/choices.rb +129 -0
- data/lib/tty2/prompt/confirm_question.rb +158 -0
- data/lib/tty2/prompt/const.rb +17 -0
- data/lib/tty2/prompt/converter_dsl.rb +21 -0
- data/lib/tty2/prompt/converter_registry.rb +69 -0
- data/lib/tty2/prompt/converters.rb +182 -0
- data/lib/tty2/prompt/distance.rb +49 -0
- data/lib/tty2/prompt/enum_list.rb +433 -0
- data/lib/tty2/prompt/errors.rb +31 -0
- data/lib/tty2/prompt/evaluator.rb +29 -0
- data/lib/tty2/prompt/expander.rb +321 -0
- data/lib/tty2/prompt/keypress.rb +98 -0
- data/lib/tty2/prompt/list.rb +589 -0
- data/lib/tty2/prompt/mask_question.rb +96 -0
- data/lib/tty2/prompt/multi_list.rb +224 -0
- data/lib/tty2/prompt/multiline.rb +72 -0
- data/lib/tty2/prompt/paginator.rb +111 -0
- data/lib/tty2/prompt/question/checks.rb +105 -0
- data/lib/tty2/prompt/question/modifier.rb +96 -0
- data/lib/tty2/prompt/question/validation.rb +72 -0
- data/lib/tty2/prompt/question.rb +391 -0
- data/lib/tty2/prompt/result.rb +42 -0
- data/lib/tty2/prompt/selected_choices.rb +77 -0
- data/lib/tty2/prompt/slider.rb +286 -0
- data/lib/tty2/prompt/statement.rb +55 -0
- data/lib/tty2/prompt/suggestion.rb +113 -0
- data/lib/tty2/prompt/symbols.rb +89 -0
- data/lib/tty2/prompt/test.rb +36 -0
- data/lib/tty2/prompt/timer.rb +75 -0
- data/lib/tty2/prompt/utils.rb +42 -0
- data/lib/tty2/prompt/version.rb +7 -0
- data/lib/tty2/prompt.rb +589 -0
- data/lib/tty2-prompt.rb +1 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a31ec4aa1ef32dbce0b75a62191dad2c4b108e7f801ba6918862a57021339acf
|
4
|
+
data.tar.gz: 7fba5b2d9b0f076dca7ef7a462c6abca5373a123538e7abc95c7821855bab79c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0b987b875d9fec6de8511467db39ea7aeff6db61561ed73490acbd7bbb69ff798ad4cc078a7bbb3af065e3702f916ff0353106e63e66615179ac790cd810b4b
|
7
|
+
data.tar.gz: 113405ed0aebead16dcc0739012e05eb7d5b430f923cba02c44b2d423e40dfc21273bcc7c741c2b247702af39a6a5ff1ec6ca626b0880b22047cfc80feb91074
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## [v0.23.1.1] - 2021-12-13
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
* Forked TTY::Prompt and reworked to TTY2::Prompt
|
9
|
+
* Change to make use of tty2-reader instead of tty-reader
|
10
|
+
|
11
|
+
### Fix
|
12
|
+
|
13
|
+
|
14
|
+
[v0.23.1.1]: https://github.com/zzyzwicz/tty2-prompt/compare/v0.23.1.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright for portions of project TTY2::Prompt are held by Piotr Murach, 2015 as part of project TTY::Prompt.
|
2
|
+
All other copyright for project TTY2::Prompt are held by zzyzwicz, 2021.
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
# TTY2::Prompt
|
3
|
+
|
4
|
+
[][gem]
|
5
|
+
[][appveyor]
|
6
|
+
|
7
|
+
[gem]: http://badge.fury.io/rb/tty2-prompt
|
8
|
+
[appveyor]: https://ci.appveyor.com/project/zzyzwicz/tty2-prompt
|
9
|
+
|
10
|
+
> A tty-prompt fork with the objective of adding a customized word completion mechanism
|
11
|
+
|
12
|
+
**TTY2::Prompt** intends to be an up to date clone of [TTY::Prompt](https://github.com/piotrmurach/tty-prompt), solely extending it with a customized word completion mechanism.
|
13
|
+
This page only covers the applied modifications.
|
14
|
+
|
15
|
+
## Modifications
|
16
|
+
|
17
|
+
* Renamed to TTY2::Prompt
|
18
|
+
* Make use of TTY2::Reader instead of TTY::Reader
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem "tty2-prompt"
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install tty2-prompt
|
35
|
+
|
36
|
+
|
37
|
+
## Contributing
|
38
|
+
|
39
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zzyzwicz/tty2-prompt.
|
40
|
+
|
41
|
+
## License
|
42
|
+
|
43
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
44
|
+
|
45
|
+
## Code of Conduct
|
46
|
+
|
47
|
+
Everyone interacting in the TTY2::Reader project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/tty-reader/blob/master/CODE_OF_CONDUCT.md).
|
48
|
+
|
49
|
+
## Copyright
|
50
|
+
|
51
|
+
Copyright for portions of project TTY2::Prompt are held by Piotr Murach, 2015 as part of project TTY::Prompt.
|
52
|
+
All other copyright for project TTY2::Prompt are held by zzyzwicz, 2021.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY2
|
4
|
+
class Prompt
|
5
|
+
class AnswersCollector
|
6
|
+
# Initialize answer collector
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
def initialize(prompt, **options)
|
10
|
+
@prompt = prompt
|
11
|
+
@answers = options.fetch(:answers) { {} }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Start gathering answers
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
# the collection of all answers
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def call(&block)
|
21
|
+
instance_eval(&block)
|
22
|
+
@answers
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create answer entry
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# key(:name).ask("Name?")
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def key(name, &block)
|
32
|
+
@name = name
|
33
|
+
if block
|
34
|
+
answer = create_collector.call(&block)
|
35
|
+
add_answer(answer)
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Change to collect all values for a key
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# key(:colors).values.ask("Color?")
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def values(&block)
|
47
|
+
@answers[@name] = Array(@answers[@name])
|
48
|
+
if block
|
49
|
+
answer = create_collector.call(&block)
|
50
|
+
add_answer(answer)
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api public
|
56
|
+
def create_collector
|
57
|
+
self.class.new(@prompt)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api public
|
61
|
+
def add_answer(answer)
|
62
|
+
if @answers[@name].is_a?(Array)
|
63
|
+
@answers[@name] << answer
|
64
|
+
else
|
65
|
+
@answers[@name] = answer
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
def method_missing(method, *args, **options, &block)
|
73
|
+
answer = @prompt.public_send(method, *args, **options, &block)
|
74
|
+
add_answer(answer)
|
75
|
+
end
|
76
|
+
end # AnswersCollector
|
77
|
+
end # Prompt
|
78
|
+
end # TTY2
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "paginator"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Prompt
|
7
|
+
class BlockPaginator < Paginator
|
8
|
+
# Paginate list of choices based on current active choice.
|
9
|
+
# Move entire pages.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def paginate(list, active, per_page = nil, &block)
|
13
|
+
default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
|
14
|
+
@per_page = @per_page || per_page || default_size
|
15
|
+
|
16
|
+
check_page_size!
|
17
|
+
|
18
|
+
# Don't paginate short lists
|
19
|
+
if list.size <= @per_page
|
20
|
+
@start_index = 0
|
21
|
+
@end_index = list.size - 1
|
22
|
+
if block
|
23
|
+
return list.each_with_index(&block)
|
24
|
+
else
|
25
|
+
return list.each_with_index.to_enum
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
unless active.nil? # User may input index out of range
|
30
|
+
@last_index = active
|
31
|
+
end
|
32
|
+
page = (@last_index / @per_page.to_f).ceil
|
33
|
+
pages = (list.size / @per_page.to_f).ceil
|
34
|
+
if page == 0
|
35
|
+
@start_index = 0
|
36
|
+
@end_index = @start_index + @per_page - 1
|
37
|
+
elsif page > 0 && page < pages
|
38
|
+
@start_index = (page - 1) * @per_page
|
39
|
+
@end_index = @start_index + @per_page - 1
|
40
|
+
elsif page == pages
|
41
|
+
@start_index = (page - 1) * @per_page
|
42
|
+
@end_index = list.size - 1
|
43
|
+
else
|
44
|
+
@end_index = list.size - 1
|
45
|
+
@start_index = @end_index - @per_page + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
sliced_list = list[@start_index..@end_index]
|
49
|
+
page_range = (@start_index..@end_index)
|
50
|
+
|
51
|
+
return sliced_list.zip(page_range).to_enum unless block_given?
|
52
|
+
|
53
|
+
sliced_list.each_with_index do |item, index|
|
54
|
+
block[item, @start_index + index]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end # EnumPaginator
|
58
|
+
end # Prompt
|
59
|
+
end # TTY2
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY2
|
4
|
+
class Prompt
|
5
|
+
# An immutable representation of a single choice option from select menu
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class Choice
|
9
|
+
# Create choice from value
|
10
|
+
#
|
11
|
+
# @examples
|
12
|
+
# Choice.from(:foo)
|
13
|
+
# # => <TTY2::Prompt::Choice @key=nil @name="foo" @value="foo" @disabled=false>
|
14
|
+
#
|
15
|
+
# Choice.from([:foo, 1])
|
16
|
+
# # => <TTY2::Prompt::Choice @key=nil @name="foo" @value=1 @disabled=false>
|
17
|
+
#
|
18
|
+
# Choice.from({name: :foo, value: 1, key: "f"}
|
19
|
+
# # => <TTY2::Prompt::Choice @key="f" @name="foo" @value=1 @disabled=false>
|
20
|
+
#
|
21
|
+
# @param [Object] val
|
22
|
+
# the value to be converted
|
23
|
+
#
|
24
|
+
# @raise [ArgumentError]
|
25
|
+
#
|
26
|
+
# @return [Choice]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def self.from(val)
|
30
|
+
case val
|
31
|
+
when Choice
|
32
|
+
val
|
33
|
+
when Array
|
34
|
+
convert_array(val)
|
35
|
+
when Hash
|
36
|
+
convert_hash(val)
|
37
|
+
else
|
38
|
+
new(val, val)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert an array into choice
|
43
|
+
#
|
44
|
+
# @param [Array<Object>]
|
45
|
+
#
|
46
|
+
# @return [Choice]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def self.convert_array(val)
|
50
|
+
name, value, options = *val
|
51
|
+
if name.is_a?(Hash)
|
52
|
+
convert_hash(name)
|
53
|
+
elsif val.size == 1
|
54
|
+
new(name.to_s, name.to_s)
|
55
|
+
else
|
56
|
+
new(name.to_s, value, **(options || {}))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Convert a hash into choice
|
61
|
+
#
|
62
|
+
# @param [Hash<Symbol,Object>]
|
63
|
+
#
|
64
|
+
# @return [Choice]
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def self.convert_hash(val)
|
68
|
+
if val.key?(:name) && val.key?(:value)
|
69
|
+
new(val[:name].to_s, val[:value], **val)
|
70
|
+
elsif val.key?(:name)
|
71
|
+
new(val[:name].to_s, val[:name].to_s, **val)
|
72
|
+
else
|
73
|
+
new(val.keys.first.to_s, val.values.first)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# The label name
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
attr_reader :name
|
81
|
+
|
82
|
+
# The keyboard key to activate this choice
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
attr_reader :key
|
86
|
+
|
87
|
+
# The text to display for disabled choice
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
attr_reader :disabled
|
91
|
+
|
92
|
+
# Create a Choice instance
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def initialize(name, value, **options)
|
96
|
+
@name = name
|
97
|
+
@value = value
|
98
|
+
@key = options[:key]
|
99
|
+
@disabled = options[:disabled].nil? ? false : options[:disabled]
|
100
|
+
freeze
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if this choice is disabled
|
104
|
+
#
|
105
|
+
# @return [Boolean]
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def disabled?
|
109
|
+
!!@disabled
|
110
|
+
end
|
111
|
+
|
112
|
+
# Read value and evaluate
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
def value
|
116
|
+
case @value
|
117
|
+
when Proc
|
118
|
+
@value.call
|
119
|
+
else
|
120
|
+
@value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Object equality comparison
|
125
|
+
#
|
126
|
+
# @return [Boolean]
|
127
|
+
#
|
128
|
+
# @api public
|
129
|
+
def ==(other)
|
130
|
+
return false unless other.is_a?(self.class)
|
131
|
+
|
132
|
+
name == other.name &&
|
133
|
+
value == other.value &&
|
134
|
+
key == other.key
|
135
|
+
end
|
136
|
+
|
137
|
+
# Object string representation
|
138
|
+
#
|
139
|
+
# @return [String]
|
140
|
+
#
|
141
|
+
# @api public
|
142
|
+
def to_s
|
143
|
+
name.to_s
|
144
|
+
end
|
145
|
+
end # Choice
|
146
|
+
end # Prompt
|
147
|
+
end # TTY2
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
require_relative "choice"
|
6
|
+
|
7
|
+
module TTY2
|
8
|
+
class Prompt
|
9
|
+
# A class responsible for storing a collection of choices
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class Choices
|
13
|
+
include Enumerable
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :choices, :length, :size, :to_ary, :empty?,
|
17
|
+
:values_at, :index, :==
|
18
|
+
|
19
|
+
# Convenience for creating choices
|
20
|
+
#
|
21
|
+
# @param [Array[Object]] choices
|
22
|
+
# the choice objects
|
23
|
+
#
|
24
|
+
# @return [Choices]
|
25
|
+
# the choices collection
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def self.[](*choices)
|
29
|
+
new(choices)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create Choices collection
|
33
|
+
#
|
34
|
+
# @param [Array[Choice]] choices
|
35
|
+
# the choices to add to collection
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def initialize(choices = [])
|
39
|
+
@choices = choices.map do |choice|
|
40
|
+
Choice.from(choice)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Scope of choices which are not disabled
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def enabled
|
48
|
+
reject(&:disabled?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def enabled_indexes
|
52
|
+
each_with_index.reduce([]) do |acc, (choice, idx)|
|
53
|
+
acc << idx unless choice.disabled?
|
54
|
+
acc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Iterate over all choices in the collection
|
59
|
+
#
|
60
|
+
# @yield [Choice]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def each(&block)
|
64
|
+
return to_enum unless block_given?
|
65
|
+
|
66
|
+
choices.each(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add choice to collection
|
70
|
+
#
|
71
|
+
# @param [Object] choice
|
72
|
+
# the choice to add
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def <<(choice)
|
76
|
+
choices << Choice.from(choice)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Access choice by index
|
80
|
+
#
|
81
|
+
# @param [Integer] index
|
82
|
+
#
|
83
|
+
# @return [Choice]
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def [](index)
|
87
|
+
@choices[index]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Pluck a choice by its name from collection
|
91
|
+
#
|
92
|
+
# @param [String] name
|
93
|
+
# the label name for the choice
|
94
|
+
#
|
95
|
+
# @return [Choice]
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def pluck(name)
|
99
|
+
map { |choice| choice.public_send(name) }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Find a matching choice
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# choices.find_by(:name, "small")
|
106
|
+
#
|
107
|
+
# @param [Symbol] attr
|
108
|
+
# the attribute name
|
109
|
+
# @param [Object] value
|
110
|
+
#
|
111
|
+
# @return [Choice]
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def find_by(attr, value)
|
115
|
+
find { |choice| choice.public_send(attr) == value }
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
# The actual collection choices
|
121
|
+
#
|
122
|
+
# @return [Array[Choice]]
|
123
|
+
#
|
124
|
+
# @api private
|
125
|
+
|
126
|
+
attr_reader :choices
|
127
|
+
end # Choices
|
128
|
+
end # Prompt
|
129
|
+
end # TTY2
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "question"
|
4
|
+
require_relative "utils"
|
5
|
+
|
6
|
+
module TTY2
|
7
|
+
class Prompt
|
8
|
+
class ConfirmQuestion < Question
|
9
|
+
# Create confirmation question
|
10
|
+
#
|
11
|
+
# @param [Hash] options
|
12
|
+
# @option options [String] :suffix
|
13
|
+
# @option options [String] :positive
|
14
|
+
# @option options [String] :negative
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def initialize(prompt, **options)
|
18
|
+
super
|
19
|
+
@suffix = options.fetch(:suffix) { UndefinedSetting }
|
20
|
+
@positive = options.fetch(:positive) { UndefinedSetting }
|
21
|
+
@negative = options.fetch(:negative) { UndefinedSetting }
|
22
|
+
end
|
23
|
+
|
24
|
+
def positive?
|
25
|
+
@positive != UndefinedSetting
|
26
|
+
end
|
27
|
+
|
28
|
+
def negative?
|
29
|
+
@negative != UndefinedSetting
|
30
|
+
end
|
31
|
+
|
32
|
+
def suffix?
|
33
|
+
@suffix != UndefinedSetting
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set question suffix
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def suffix(value = (not_set = true))
|
40
|
+
return @negative if not_set
|
41
|
+
|
42
|
+
@suffix = value
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set value for matching positive choice
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def positive(value = (not_set = true))
|
49
|
+
return @positive if not_set
|
50
|
+
|
51
|
+
@positive = value
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set value for matching negative choice
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def negative(value = (not_set = true))
|
58
|
+
return @negative if not_set
|
59
|
+
|
60
|
+
@negative = value
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(message, &block)
|
64
|
+
return if Utils.blank?(message)
|
65
|
+
|
66
|
+
@message = message
|
67
|
+
block.call(self) if block
|
68
|
+
setup_defaults
|
69
|
+
render
|
70
|
+
end
|
71
|
+
|
72
|
+
# Render confirmation question
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
def render_question
|
78
|
+
header = "#{@prefix}#{message} "
|
79
|
+
if !@done
|
80
|
+
header += @prompt.decorate("(#{@suffix})", @help_color) + " "
|
81
|
+
else
|
82
|
+
answer = conversion.call(@input)
|
83
|
+
label = answer ? @positive : @negative
|
84
|
+
header += @prompt.decorate(label, @active_color)
|
85
|
+
end
|
86
|
+
header << "\n" if @done
|
87
|
+
header
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Decide how to handle input from user
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def process_input(question)
|
96
|
+
@input = read_input(question)
|
97
|
+
if Utils.blank?(@input)
|
98
|
+
@input = default ? positive : negative
|
99
|
+
end
|
100
|
+
@evaluator.call(@input)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def setup_defaults
|
105
|
+
infer_default
|
106
|
+
@convert = conversion
|
107
|
+
return if suffix? && positive?
|
108
|
+
|
109
|
+
if suffix? && (!positive? || !negative?)
|
110
|
+
parts = @suffix.split("/")
|
111
|
+
@positive = parts[0]
|
112
|
+
@negative = parts[1]
|
113
|
+
elsif !suffix? && positive?
|
114
|
+
@suffix = create_suffix
|
115
|
+
else
|
116
|
+
create_default_labels
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @api private
|
121
|
+
def infer_default
|
122
|
+
converted = Converters.convert(:bool, default.to_s)
|
123
|
+
if converted == Const::Undefined
|
124
|
+
raise InvalidArgument, "default needs to be `true` or `false`"
|
125
|
+
else
|
126
|
+
default(converted)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# @api private
|
131
|
+
def create_default_labels
|
132
|
+
@suffix = default ? "Y/n" : "y/N"
|
133
|
+
@positive = default ? "Yes" : "yes"
|
134
|
+
@negative = default ? "no" : "No"
|
135
|
+
@validation = /^(y(es)?|no?)$/i
|
136
|
+
@messages[:valid?] = "Invalid input."
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
def create_suffix
|
141
|
+
(default ? positive.capitalize : positive.downcase) + "/" +
|
142
|
+
(default ? negative.downcase : negative.capitalize)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Create custom conversion
|
146
|
+
#
|
147
|
+
# @api private
|
148
|
+
def conversion
|
149
|
+
->(input) do
|
150
|
+
positive_word = Regexp.escape(positive)
|
151
|
+
positive_letter = Regexp.escape(positive[0])
|
152
|
+
pattern = Regexp.new("^(#{positive_word}|#{positive_letter})$", true)
|
153
|
+
!input.match(pattern).nil?
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end # ConfirmQuestion
|
157
|
+
end # Prompt
|
158
|
+
end # TTY2
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY2
|
4
|
+
class Prompt
|
5
|
+
module Const
|
6
|
+
Undefined = Object.new.tap do |obj|
|
7
|
+
def obj.to_s
|
8
|
+
"undefined"
|
9
|
+
end
|
10
|
+
|
11
|
+
def obj.inspect
|
12
|
+
"undefined".inspect
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end # Const
|
16
|
+
end # Prompt
|
17
|
+
end # TTY2
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "converter_registry"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Prompt
|
7
|
+
module ConverterDSL
|
8
|
+
def converter_registry
|
9
|
+
@__converter_registry ||= ConverterRegistry.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def converter(*names, &block)
|
13
|
+
converter_registry.register(*names, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def convert(name, input)
|
17
|
+
converter_registry[name].call(input)
|
18
|
+
end
|
19
|
+
end # ConverterDSL
|
20
|
+
end # Prompt
|
21
|
+
end # TTY2
|