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
@@ -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
|