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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.md +52 -0
  5. data/lib/tty2/prompt/answers_collector.rb +78 -0
  6. data/lib/tty2/prompt/block_paginator.rb +59 -0
  7. data/lib/tty2/prompt/choice.rb +147 -0
  8. data/lib/tty2/prompt/choices.rb +129 -0
  9. data/lib/tty2/prompt/confirm_question.rb +158 -0
  10. data/lib/tty2/prompt/const.rb +17 -0
  11. data/lib/tty2/prompt/converter_dsl.rb +21 -0
  12. data/lib/tty2/prompt/converter_registry.rb +69 -0
  13. data/lib/tty2/prompt/converters.rb +182 -0
  14. data/lib/tty2/prompt/distance.rb +49 -0
  15. data/lib/tty2/prompt/enum_list.rb +433 -0
  16. data/lib/tty2/prompt/errors.rb +31 -0
  17. data/lib/tty2/prompt/evaluator.rb +29 -0
  18. data/lib/tty2/prompt/expander.rb +321 -0
  19. data/lib/tty2/prompt/keypress.rb +98 -0
  20. data/lib/tty2/prompt/list.rb +589 -0
  21. data/lib/tty2/prompt/mask_question.rb +96 -0
  22. data/lib/tty2/prompt/multi_list.rb +224 -0
  23. data/lib/tty2/prompt/multiline.rb +72 -0
  24. data/lib/tty2/prompt/paginator.rb +111 -0
  25. data/lib/tty2/prompt/question/checks.rb +105 -0
  26. data/lib/tty2/prompt/question/modifier.rb +96 -0
  27. data/lib/tty2/prompt/question/validation.rb +72 -0
  28. data/lib/tty2/prompt/question.rb +391 -0
  29. data/lib/tty2/prompt/result.rb +42 -0
  30. data/lib/tty2/prompt/selected_choices.rb +77 -0
  31. data/lib/tty2/prompt/slider.rb +286 -0
  32. data/lib/tty2/prompt/statement.rb +55 -0
  33. data/lib/tty2/prompt/suggestion.rb +113 -0
  34. data/lib/tty2/prompt/symbols.rb +89 -0
  35. data/lib/tty2/prompt/test.rb +36 -0
  36. data/lib/tty2/prompt/timer.rb +75 -0
  37. data/lib/tty2/prompt/utils.rb +42 -0
  38. data/lib/tty2/prompt/version.rb +7 -0
  39. data/lib/tty2/prompt.rb +589 -0
  40. data/lib/tty2-prompt.rb +1 -0
  41. 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 Version](https://badge.fury.io/rb/tty2-prompt.svg)][gem]
5
+ [![Build status](https://ci.appveyor.com/api/projects/status/570nk071y68idah1?svg=true)][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