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
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module TTY2
6
+ class Prompt
7
+ # Immutable collection of converters for type transformation
8
+ #
9
+ # @api private
10
+ class ConverterRegistry
11
+ extend Forwardable
12
+
13
+ def_delegators "@__registry", :keys
14
+
15
+ # Create a registry of conversions
16
+ #
17
+ # @param [Hash] registry
18
+ #
19
+ # @api private
20
+ def initialize(registry = {})
21
+ @__registry = registry.dup
22
+ end
23
+
24
+ # Check if conversion is available
25
+ #
26
+ # @param [String] name
27
+ #
28
+ # @return [Boolean]
29
+ #
30
+ # @api public
31
+ def contain?(name)
32
+ conv_name = name.to_s.downcase.to_sym
33
+ @__registry.key?(conv_name)
34
+ end
35
+
36
+ # Register a conversion
37
+ #
38
+ # @param [Symbol] name
39
+ # the converter name
40
+ #
41
+ # @api public
42
+ def register(*names, &block)
43
+ names.each do |name|
44
+ if contain?(name)
45
+ raise ConversionAlreadyDefined,
46
+ "converter for #{name.inspect} is already registered"
47
+ end
48
+ @__registry[name] = block
49
+ end
50
+ end
51
+
52
+ # Execute converter
53
+ #
54
+ # @api public
55
+ def [](name)
56
+ conv_name = name.to_s.downcase.to_sym
57
+ @__registry.fetch(conv_name) do
58
+ raise UnsupportedConversion,
59
+ "converter #{conv_name.inspect} is not registered"
60
+ end
61
+ end
62
+ alias fetch []
63
+
64
+ def inspect
65
+ @_registry.inspect
66
+ end
67
+ end # ConverterRegistry
68
+ end # Prompt
69
+ end # TTY2
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "const"
4
+ require_relative "converter_dsl"
5
+
6
+ module TTY2
7
+ class Prompt
8
+ module Converters
9
+ extend ConverterDSL
10
+
11
+ TRUE_VALUES = /^(t(rue)?|y(es)?|on|1)$/i.freeze
12
+ FALSE_VALUES = /^(f(alse)?|n(o)?|off|0)$/i.freeze
13
+
14
+ SINGLE_DIGIT_MATCHER = /^(?<digit>\-?\d+(\.\d+)?)$/.freeze
15
+ DIGIT_MATCHER = /^(?<open>-?\d+(\.\d+)?)
16
+ \s*(?<sep>(\.\s*){2,3}|-|,)\s*
17
+ (?<close>-?\d+(\.\d+)?)$
18
+ /x.freeze
19
+ LETTER_MATCHER = /^(?<open>\w)
20
+ \s*(?<sep>(\.\s*){2,3}|-|,)\s*
21
+ (?<close>\w)$
22
+ /x.freeze
23
+
24
+ converter(:boolean, :bool) do |input|
25
+ case input.to_s
26
+ when TRUE_VALUES then true
27
+ when FALSE_VALUES then false
28
+ else Const::Undefined
29
+ end
30
+ end
31
+
32
+ converter(:string, :str) do |input|
33
+ String(input).chomp
34
+ end
35
+
36
+ converter(:symbol, :sym) do |input|
37
+ input.to_sym
38
+ end
39
+
40
+ converter(:char) do |input|
41
+ String(input).chars.to_a[0]
42
+ end
43
+
44
+ converter(:date) do |input|
45
+ begin
46
+ require "date" unless defined?(::Date)
47
+ ::Date.parse(input)
48
+ rescue ArgumentError
49
+ Const::Undefined
50
+ end
51
+ end
52
+
53
+ converter(:datetime) do |input|
54
+ begin
55
+ require "date" unless defined?(::Date)
56
+ ::DateTime.parse(input.to_s)
57
+ rescue ArgumentError
58
+ Const::Undefined
59
+ end
60
+ end
61
+
62
+ converter(:time) do |input|
63
+ begin
64
+ require "time"
65
+ ::Time.parse(input.to_s)
66
+ rescue ArgumentError
67
+ Const::Undefined
68
+ end
69
+ end
70
+
71
+ converter(:integer, :int) do |input|
72
+ begin
73
+ Integer(input)
74
+ rescue ArgumentError
75
+ Const::Undefined
76
+ end
77
+ end
78
+
79
+ converter(:float) do |input|
80
+ begin
81
+ Float(input)
82
+ rescue TypeError, ArgumentError
83
+ Const::Undefined
84
+ end
85
+ end
86
+
87
+ # Convert string number to integer or float
88
+ #
89
+ # @return [Integer,Float,Const::Undefined]
90
+ #
91
+ # @api private
92
+ def cast_to_num(num)
93
+ ([convert(:int, num), convert(:float, num)] - [Const::Undefined]).first ||
94
+ Const::Undefined
95
+ end
96
+ module_function :cast_to_num
97
+
98
+ converter(:range) do |input|
99
+ if input.is_a?(::Range)
100
+ input
101
+ elsif match = input.to_s.match(SINGLE_DIGIT_MATCHER)
102
+ digit = cast_to_num(match[:digit])
103
+ ::Range.new(digit, digit)
104
+ elsif match = input.to_s.match(DIGIT_MATCHER)
105
+ open = cast_to_num(match[:open])
106
+ close = cast_to_num(match[:close])
107
+ ::Range.new(open, close, match[:sep].gsub(/\s*/, "") == "...")
108
+ elsif match = input.to_s.match(LETTER_MATCHER)
109
+ ::Range.new(match[:open], match[:close],
110
+ match[:sep].gsub(/\s*/, "") == "...")
111
+ else Const::Undefined
112
+ end
113
+ end
114
+
115
+ converter(:regexp) do |input|
116
+ Regexp.new(input)
117
+ end
118
+
119
+ converter(:filepath, :file) do |input|
120
+ ::File.expand_path(input)
121
+ end
122
+
123
+ converter(:pathname, :path) do |input|
124
+ require "pathname" unless defined?(::Pathname)
125
+ ::Pathname.new(input)
126
+ end
127
+
128
+ converter(:uri) do |input|
129
+ require "uri" unless defined?(::URI)
130
+ ::URI.parse(input)
131
+ end
132
+
133
+ converter(:list, :array) do |val|
134
+ (val.respond_to?(:to_a) ? val : val.split(/(?<!\\),/))
135
+ .map { |v| v.strip.gsub(/\\,/, ",") }
136
+ .reject(&:empty?)
137
+ end
138
+
139
+ converter(:hash, :map) do |val|
140
+ values = val.respond_to?(:to_a) ? val : val.split(/[& ]/)
141
+ values.each_with_object({}) do |pair, pairs|
142
+ key, value = pair.split(/[=:]/, 2)
143
+ if (current = pairs[key.to_sym])
144
+ pairs[key.to_sym] = Array(current) << value
145
+ else
146
+ pairs[key.to_sym] = value
147
+ end
148
+ pairs
149
+ end
150
+ end
151
+
152
+ converter_registry.keys.each do |type|
153
+ next if type =~ /list|array|map|hash/
154
+
155
+ [:"#{type}_list", :"#{type}_array", :"#{type}s"].each do |new_type|
156
+ converter(new_type) do |val|
157
+ converter_registry[:array].(val).map do |obj|
158
+ converter_registry[type].(obj)
159
+ end
160
+ end
161
+ end
162
+
163
+ [:"#{type}_map", :"#{type}_hash"].each do |new_type|
164
+ converter(new_type) do |val|
165
+ converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
166
+ h[k] = converter_registry[type].(v)
167
+ end
168
+ end
169
+ end
170
+
171
+ [:"string_#{type}_map", :"str_#{type}_map",
172
+ :"string_#{type}_hash", :"str_#{type}_hash"].each do |new_type|
173
+ converter(new_type) do |val|
174
+ converter_registry[:hash].(val).each_with_object({}) do |(k, v), h|
175
+ h[converter_registry[:string].(k)] = converter_registry[type].(v)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end # Converters
181
+ end # Prompt
182
+ end # TTY2
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY2
4
+ class Prompt
5
+ # A class responsible for string comparison
6
+ class Distance
7
+ # Calculate the optimal string alignment distance
8
+ #
9
+ # @api public
10
+ def distance(first, second)
11
+ distances = []
12
+ rows = first.to_s.length
13
+ cols = second.to_s.length
14
+
15
+ 0.upto(rows) do |index|
16
+ distances << [index] + [0] * cols
17
+ end
18
+ distances[0] = 0.upto(cols).to_a
19
+
20
+ 1.upto(rows) do |first_index|
21
+ 1.upto(cols) do |second_index|
22
+ first_char = first[first_index - 1]
23
+ second_char = second[second_index - 1]
24
+ cost = first_char == second_char ? 0 : 1
25
+
26
+ distances[first_index][second_index] = [
27
+ distances[first_index - 1][second_index], # deletion
28
+ distances[first_index][second_index - 1], # insertion
29
+ distances[first_index - 1][second_index - 1] # substitution
30
+ ].min + cost
31
+
32
+ if first_index > 1 && second_index > 1
33
+ first_previous_char = first[first_index - 2]
34
+ second_previous_char = second[second_index - 2]
35
+ if first_char == second_previous_char && second_char == first_previous_char
36
+ distances[first_index][second_index] = [
37
+ distances[first_index][second_index],
38
+ distances[first_index - 2][second_index - 2] + 1 # transposition
39
+ ].min
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ distances[rows][cols]
46
+ end
47
+ end # Distance
48
+ end # Prompt
49
+ end # TTY2