cli_miami 1.0.3.pre → 1.0.5.pre
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 +4 -4
- data/README.md +4 -4
- data/i18n/en.yml +21 -13
- data/lib/cli_miami/ask.rb +94 -17
- data/lib/cli_miami/error.rb +17 -6
- data/lib/cli_miami/global.rb +5 -0
- data/lib/cli_miami/say.rb +1 -1
- data/lib/cli_miami/validation.rb +7 -0
- data/lib/cli_miami.rb +201 -8
- data/spec/fixtures/i18n.yml +3 -0
- data/spec/lib/cli_miami/ask_spec.rb +62 -0
- data/spec/lib/cli_miami/error_spec.rb +1 -1
- data/spec/lib/cli_miami/validation_spec.rb +6 -6
- data/spec/lib/cli_miami_spec.rb +26 -10
- metadata +3 -4
- data/lib/cli_miami/core.rb +0 -84
- data/lib/namespaced.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42418b61ff38c853438626c7bc70ad533ce728d7
|
4
|
+
data.tar.gz: 8cc0c02b300bd52c9d15969edd95f88468717645
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3df0997f3ba2a4e57859962b3024c3f488a04ffd59fccf3e8eca8d0a29cd5392aa70caac8240abcb7f6b503842e530449e9976a4ea38848b69c28c8902707ebc
|
7
|
+
data.tar.gz: 2aba81627198c963ab0c46d44d2135cb0e7b492fec548927643d5d25251326ce500b87e4368e48e3018c38af5d9c26a2dffc16b559f4601408df380c9b088222
|
data/README.md
CHANGED
@@ -15,11 +15,11 @@ gem install cli_miami
|
|
15
15
|
|
16
16
|
_Gemfile_
|
17
17
|
```ruby
|
18
|
-
#
|
18
|
+
# For the name-spaced methods CliMiami::A.sk & CliMiami::S.ay...
|
19
19
|
gem 'cli_miami'
|
20
20
|
|
21
|
-
#
|
22
|
-
gem 'cli_miami', :
|
21
|
+
# For the friendlier global methods A.sk & S.ay...
|
22
|
+
gem 'cli_miami', require: 'cli_miami/global'
|
23
23
|
```
|
24
24
|
|
25
25
|
#### S.ay
|
@@ -59,7 +59,7 @@ Both `S.ay` and `A.sk` suport the same options
|
|
59
59
|
color: => [symbol] # See ansi color codes below
|
60
60
|
bgcolor: => [symbol] # See ansi color codes below
|
61
61
|
style: => [symbol] # See ansi style codes below. Can accept multiple styles as an array
|
62
|
-
justify: => [center|
|
62
|
+
justify: => [center|left|right] # The type of justification to use
|
63
63
|
padding: => [integer] # The maximum string size to justify text in
|
64
64
|
indent: => [integer] # The number of characters to indent
|
65
65
|
newline: => [boolean] # True if you want a newline after the output
|
data/i18n/en.yml
CHANGED
@@ -3,32 +3,40 @@ en:
|
|
3
3
|
# CliMiami core library strings
|
4
4
|
core:
|
5
5
|
empty: '[EMPTY]'
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
enter_empty_value: Press Enter when finished...
|
7
|
+
enter_end_value: Enter the maximum value
|
8
|
+
enter_start_value: Enter the minimum value
|
9
|
+
enter_value_for: "Enter a value for %{key}"
|
10
|
+
|
11
|
+
# TYPE SPECIFIC MESSAGES
|
12
|
+
multiple_choice:
|
13
|
+
prompt: 'Choose one of the following options:'
|
14
|
+
selected_choices: Selected Options
|
10
15
|
|
11
16
|
errors:
|
12
17
|
# generic error type messages
|
13
|
-
convert: "
|
14
|
-
length: "
|
15
|
-
regexp: "
|
18
|
+
convert: "%{value} cannot be converted to a %{type} %{description}"
|
19
|
+
length: "%{value} has a length of %{value_length}, but must be between %{allowed_values} %{description}"
|
20
|
+
regexp: "%{value} does not match the required format of %{allowed_values} %{description}"
|
16
21
|
type: "Unknown type %{type}"
|
17
22
|
|
18
23
|
# specific value type messages
|
19
24
|
# these messages will take precedence over the general messages above
|
20
25
|
array:
|
21
|
-
length: "
|
26
|
+
length: "You entered %{value_length} items, but you need between %{allowed_values} %{description}"
|
22
27
|
boolean:
|
23
28
|
file:
|
24
|
-
validate: "
|
29
|
+
validate: "%{value} is not a valid path %{description}"
|
25
30
|
fixnum:
|
26
|
-
length: "
|
31
|
+
length: "%{value} is invalid. Enter a number between %{allowed_values} %{description}"
|
27
32
|
float:
|
28
|
-
length: "
|
33
|
+
length: "%{value} is invalid. Enter a number between %{allowed_values} %{description}"
|
29
34
|
hash:
|
30
|
-
length: "
|
31
|
-
keys: "
|
35
|
+
length: "You entered %{value_length} items, but you need between %{allowed_values} %{description}"
|
36
|
+
keys: "You are missing values for %{keys} %{description}"
|
37
|
+
multiple_choice:
|
38
|
+
length: "You have selected %{value_length} options, but you need between %{allowed_values} %{description}"
|
39
|
+
delete_required_key: "You cannot delete the required key %{key}"
|
32
40
|
range:
|
33
41
|
string:
|
34
42
|
symbol:
|
data/lib/cli_miami/ask.rb
CHANGED
@@ -21,11 +21,11 @@ private
|
|
21
21
|
def initialize question, options
|
22
22
|
options = CliMiami.get_options options
|
23
23
|
|
24
|
-
# add description to question
|
25
|
-
question << ' (' << options[:description] << ')'
|
26
|
-
|
27
24
|
# display question
|
28
|
-
CliMiami::S.ay question, options
|
25
|
+
CliMiami::S.ay question, options.merge(preset: :cli_miami_instruction)
|
26
|
+
|
27
|
+
# display description
|
28
|
+
CliMiami::S.ay options[:description], preset: :cli_miami_instruction_sub, indent: 4 if options[:description]
|
29
29
|
|
30
30
|
# request given type to user
|
31
31
|
@value = request_type options
|
@@ -45,7 +45,7 @@ private
|
|
45
45
|
def request_type options
|
46
46
|
send("request_#{options[:type]}", options)
|
47
47
|
rescue
|
48
|
-
CliMiami::S.ay I18n.t('cli_miami.errors.type', options)
|
48
|
+
CliMiami::S.ay I18n.t('cli_miami.errors.type', options.merge(preset: :cli_miami_fail))
|
49
49
|
end
|
50
50
|
|
51
51
|
# for most types, a simple validation check is all that is needed
|
@@ -86,7 +86,7 @@ private
|
|
86
86
|
alias_method :request_string, :request_until_valid
|
87
87
|
alias_method :request_symbol, :request_until_valid
|
88
88
|
|
89
|
-
# rubocop:disable Metrics/MethodLength
|
89
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
90
90
|
def request_array options
|
91
91
|
array = []
|
92
92
|
value_options = CliMiami.get_options(options[:value_options] || {})
|
@@ -96,21 +96,83 @@ private
|
|
96
96
|
while array.length < options[:max]
|
97
97
|
response = request_until_valid value_options, true
|
98
98
|
|
99
|
+
# user attempting to finish entering items
|
99
100
|
if response.empty?
|
100
101
|
break if CliMiami::Validation.new(array, options).valid?
|
101
102
|
redo
|
103
|
+
|
104
|
+
# remove item if user enters it twice
|
105
|
+
elsif array.include? response
|
106
|
+
array.delete response
|
107
|
+
|
108
|
+
# add response to the list
|
102
109
|
else
|
103
110
|
array << response
|
104
111
|
end
|
105
112
|
|
106
113
|
# update user
|
107
|
-
CliMiami::S.ay array.to_sentence, :
|
114
|
+
CliMiami::S.ay array.to_sentence, :cli_miami_update
|
108
115
|
end
|
109
116
|
|
110
117
|
array
|
111
118
|
end
|
119
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
120
|
+
|
121
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
122
|
+
def request_multiple_choice options
|
123
|
+
selected_choices = nil
|
124
|
+
selected_choice_indexes = []
|
125
|
+
|
126
|
+
while selected_choice_indexes.length < options[:max]
|
127
|
+
# quit if all the options have been selected
|
128
|
+
break if selected_choice_indexes.length == options[:choices].length
|
129
|
+
|
130
|
+
# display options
|
131
|
+
CliMiami::S.ay
|
132
|
+
CliMiami::S.ay I18n.t('cli_miami.core.multiple_choice.prompt'), :cli_miami_instruction
|
133
|
+
options[:choices].each_with_index do |li, i|
|
134
|
+
# show already selected choices with a different color
|
135
|
+
choice_text = "#{(i + 1).to_s.rjust(3)}: #{li}"
|
136
|
+
choice_text_color = selected_choice_indexes.include?(i) ? :black : :cyan
|
137
|
+
CliMiami::S.ay choice_text, style: :bright, color: choice_text_color
|
138
|
+
end
|
139
|
+
|
140
|
+
# request a response from the user (return fixnum)
|
141
|
+
response = request_until_valid({
|
142
|
+
type: :fixnum,
|
143
|
+
min: 1,
|
144
|
+
max: options[:choices].length
|
145
|
+
}, true)
|
146
|
+
|
147
|
+
if response == ''
|
148
|
+
break if CliMiami::Validation.new(selected_choice_indexes, options).valid?
|
149
|
+
redo
|
150
|
+
else
|
151
|
+
# convert human readable response to array index
|
152
|
+
response_index = response - 1
|
153
|
+
|
154
|
+
# add choice to array
|
155
|
+
if selected_choice_indexes.include? response_index
|
156
|
+
selected_choice_indexes.delete response_index
|
157
|
+
else
|
158
|
+
selected_choice_indexes << response_index
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# update user
|
163
|
+
CliMiami::S.ay
|
164
|
+
CliMiami::S.ay I18n.t('cli_miami.core.multiple_choice.selected_choices'), :cli_miami_instruction_sub
|
165
|
+
selected_choices = selected_choice_indexes.map{ |i| options[:choices][i] }
|
166
|
+
selected_choices.each do |sc|
|
167
|
+
CliMiami::S.ay "• #{sc}", preset: :cli_miami_update, indent: 3
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
selected_choices
|
172
|
+
end
|
173
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
112
174
|
|
113
|
-
# rubocop:disable Metrics/AbcSize, Metrics/BlockNesting, Metrics/PerceivedComplexity
|
175
|
+
# rubocop:disable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
114
176
|
def request_hash options
|
115
177
|
hash = {}
|
116
178
|
options[:keys] ||= []
|
@@ -126,7 +188,7 @@ private
|
|
126
188
|
hash[key.to_sym] = request_until_valid value_options
|
127
189
|
|
128
190
|
# update user
|
129
|
-
CliMiami::S.ay hash.
|
191
|
+
CliMiami::S.ay hash.to_cli_miami_string, :cli_miami_update
|
130
192
|
end
|
131
193
|
|
132
194
|
# set boolean so we know all required keys are set
|
@@ -146,20 +208,35 @@ private
|
|
146
208
|
break if CliMiami::Validation.new(hash, options).valid?
|
147
209
|
redo
|
148
210
|
else
|
211
|
+
CliMiami::S.ay I18n.t('cli_miami.core.enter_value_for', key: user_key), :cli_miami_instruction
|
212
|
+
|
149
213
|
# request value
|
150
|
-
|
151
|
-
|
214
|
+
user_value = request_until_valid value_options, true
|
215
|
+
|
216
|
+
if user_value.empty?
|
217
|
+
if options[:keys].include? user_key
|
218
|
+
# prevent deleting required keys
|
219
|
+
CliMiami::S.ay I18n.t('cli_miami.errors.multiple_choice.delete_required_key', key: user_key), :cli_miami_fail
|
220
|
+
redo
|
221
|
+
else
|
222
|
+
# delete user-specified key
|
223
|
+
hash.delete user_key
|
224
|
+
end
|
225
|
+
else
|
226
|
+
# set user-defined key
|
227
|
+
hash[user_key] = user_value
|
228
|
+
end
|
152
229
|
end
|
153
230
|
end
|
154
231
|
|
155
|
-
CliMiami::S.ay hash.
|
232
|
+
CliMiami::S.ay hash.to_cli_miami_string, :cli_miami_update
|
156
233
|
end
|
157
234
|
|
158
235
|
hash
|
159
236
|
end
|
160
|
-
# rubocop:enable Metrics/AbcSize, Metrics/BlockNesting, Metrics/PerceivedComplexity
|
237
|
+
# rubocop:enable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
161
238
|
|
162
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
239
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
163
240
|
def request_range options
|
164
241
|
start_value = nil
|
165
242
|
end_value = nil
|
@@ -167,13 +244,13 @@ private
|
|
167
244
|
|
168
245
|
# get start value
|
169
246
|
until (Float(start_value) rescue nil)
|
170
|
-
CliMiami::S.ay I18n.t('cli_miami.core.enter_start_value'), preset: :
|
247
|
+
CliMiami::S.ay I18n.t('cli_miami.core.enter_start_value'), preset: :cli_miami_instruction, newline: false
|
171
248
|
start_value = request_until_valid range_value_options
|
172
249
|
end
|
173
250
|
|
174
251
|
# get end value
|
175
252
|
until (Float(end_value) rescue nil)
|
176
|
-
CliMiami::S.ay I18n.t('cli_miami.core.enter_end_value'), preset: :
|
253
|
+
CliMiami::S.ay I18n.t('cli_miami.core.enter_end_value'), preset: :cli_miami_instruction, newline: false
|
177
254
|
end_value = request_until_valid range_value_options
|
178
255
|
end
|
179
256
|
|
@@ -190,5 +267,5 @@ private
|
|
190
267
|
request_range options
|
191
268
|
end
|
192
269
|
end
|
193
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
270
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
194
271
|
end
|
data/lib/cli_miami/error.rb
CHANGED
@@ -69,7 +69,7 @@ private
|
|
69
69
|
all_boolean_values = CliMiami::BOOLEAN_TRUE_VALUES + CliMiami::BOOLEAN_FALSE_VALUES
|
70
70
|
all_boolean_values.to_sentence
|
71
71
|
|
72
|
-
when :fixnum, :float, :range, :string, :symbol
|
72
|
+
when :fixnum, :float, :multiple_choice, :range, :string, :symbol
|
73
73
|
"#{options[:min]}-#{options[:max]}"
|
74
74
|
|
75
75
|
when :regexp
|
@@ -83,26 +83,37 @@ private
|
|
83
83
|
|
84
84
|
# build an i18n dot notation string to use for lookup in the language yaml
|
85
85
|
#
|
86
|
-
# rubocop:disable
|
86
|
+
# rubocop:disable Metrics/MethodLength
|
87
87
|
def i18n_lookup_keys options, *error_keys
|
88
88
|
# check for type specific error first, then look for generic errors
|
89
|
-
i18n_lookup(
|
89
|
+
i18n_lookup(
|
90
|
+
options,
|
90
91
|
'cli_miami',
|
91
92
|
'errors',
|
92
93
|
options[:type].to_s,
|
93
94
|
*error_keys
|
94
|
-
) || i18n_lookup(
|
95
|
+
) || i18n_lookup(
|
96
|
+
options,
|
95
97
|
'cli_miami',
|
96
98
|
'errors',
|
97
99
|
*error_keys
|
98
100
|
)
|
99
101
|
end
|
100
|
-
# rubocop:enable
|
102
|
+
# rubocop:enable Metrics/MethodLength
|
101
103
|
|
102
104
|
# use i18n dot notation keys to lookup string
|
103
105
|
#
|
104
106
|
def i18n_lookup options, *keys
|
105
107
|
i18n_string = keys.flatten.compact.each(&:to_s).join('.')
|
106
|
-
|
108
|
+
|
109
|
+
# check if value exists, and translate it
|
110
|
+
if I18n.exists? i18n_string
|
111
|
+
# format description for i18n messages
|
112
|
+
if options[:description]
|
113
|
+
options[:description] = ' (' << options[:description] << ')'
|
114
|
+
end
|
115
|
+
|
116
|
+
I18n.t i18n_string, options
|
117
|
+
end
|
107
118
|
end
|
108
119
|
end
|
data/lib/cli_miami/say.rb
CHANGED
data/lib/cli_miami/validation.rb
CHANGED
@@ -71,6 +71,9 @@ private
|
|
71
71
|
Float string
|
72
72
|
end
|
73
73
|
|
74
|
+
# multiple choice strings are also integers
|
75
|
+
alias_method :convert_string_to_multiple_choice, :convert_string_to_fixnum
|
76
|
+
|
74
77
|
def convert_string_to_range string, _options
|
75
78
|
range_array = string.split('..').map { |i| Float(i) }.sort
|
76
79
|
|
@@ -172,6 +175,10 @@ private
|
|
172
175
|
end
|
173
176
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
174
177
|
|
178
|
+
def validate_multiple_choice selections, options
|
179
|
+
validate_length selections, selections.length, options
|
180
|
+
end
|
181
|
+
|
175
182
|
def validate_range range, options
|
176
183
|
range_diff = (range.max - range.min) + 1
|
177
184
|
validate_length range, range_diff, options
|
data/lib/cli_miami.rb
CHANGED
@@ -1,12 +1,205 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# load dependencies
|
3
3
|
#
|
4
|
-
|
5
|
-
|
4
|
+
require 'active_support/core_ext/hash/conversions'
|
5
|
+
require 'active_support/core_ext/hash/keys'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
7
|
+
require 'i18n'
|
8
|
+
require 'readline'
|
9
|
+
|
10
|
+
# open Hash class
|
11
|
+
#
|
12
|
+
class Hash
|
13
|
+
def to_cli_miami_string
|
14
|
+
map do |k, v|
|
15
|
+
"#{k}: #{v}"
|
16
|
+
end.to_sentence
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# open String class
|
21
|
+
#
|
22
|
+
class String
|
23
|
+
def method_missing method, *args
|
24
|
+
method_string = method.to_s.dup
|
25
|
+
|
26
|
+
if method_string.slice! 'cli_miami_'
|
27
|
+
preset = CliMiami.presets[method_string.to_sym]
|
28
|
+
options = preset.merge args[0] || {}
|
29
|
+
CliMiami::S.ay self, options
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# i18n
|
37
|
+
#
|
38
|
+
# load yml locales included in the cli miami gem
|
39
|
+
# [PATH TO GEMS]/cli_miami/i18n/en.yml
|
40
|
+
I18n.load_path += Dir["#{File.dirname(__FILE__)}/../i18n/*.yml"]
|
41
|
+
# load locale in current directory named `i18n.yml`
|
42
|
+
# ./i18n.yml
|
43
|
+
I18n.load_path += Dir['./i18n.yml']
|
44
|
+
# load locales in the folder `i18n` in the current directory
|
45
|
+
# ./i18n/en.yml
|
46
|
+
I18n.load_path += Dir['./i18n/*.yml']
|
47
|
+
|
48
|
+
# readline
|
49
|
+
#
|
50
|
+
Readline.completion_append_character = '/'
|
51
|
+
|
52
|
+
# creates Boolean type that true/false inherit
|
53
|
+
# this allows true/false to respond to a single Boolean class to check for
|
6
54
|
#
|
7
|
-
#
|
8
|
-
|
55
|
+
# rubocop:disable Style/Documentation
|
56
|
+
module Boolean; end
|
57
|
+
class TrueClass; include Boolean; end
|
58
|
+
class FalseClass; include Boolean; end
|
59
|
+
# rubocop:enable Style/Documentation
|
60
|
+
|
61
|
+
# create Cli Miami namespace and load library
|
62
|
+
#
|
63
|
+
module CliMiami
|
64
|
+
require 'cli_miami/ask'
|
65
|
+
require 'cli_miami/error'
|
66
|
+
require 'cli_miami/say'
|
67
|
+
require 'cli_miami/validation'
|
68
|
+
|
69
|
+
BOOLEAN_TRUE_VALUES = %w(true t yes y).freeze
|
70
|
+
BOOLEAN_FALSE_VALUES = %w(false f no n).freeze
|
71
|
+
|
72
|
+
# keys: type values allowed through the API
|
73
|
+
# values: type used to validate internally in the Validation class
|
74
|
+
TYPE_MAP = {
|
75
|
+
array: :array,
|
76
|
+
boolean: :boolean,
|
77
|
+
dir: :file,
|
78
|
+
directory: :file,
|
79
|
+
file: :file,
|
80
|
+
fixnum: :fixnum,
|
81
|
+
float: :float,
|
82
|
+
folder: :file,
|
83
|
+
multiple_choice: :multiple_choice,
|
84
|
+
hash: :hash,
|
85
|
+
integer: :fixnum,
|
86
|
+
list: :array,
|
87
|
+
number: :fixnum,
|
88
|
+
object: :hash,
|
89
|
+
path: :file,
|
90
|
+
range: :range,
|
91
|
+
string: :string,
|
92
|
+
symbol: :symbol
|
93
|
+
}.freeze
|
94
|
+
|
95
|
+
# default presets
|
96
|
+
# rubocop:disable Style/ClassVars
|
97
|
+
@@presets = {
|
98
|
+
cli_miami_fail: {
|
99
|
+
indent: 2,
|
100
|
+
color: :red,
|
101
|
+
style: [:bold, :underline]
|
102
|
+
},
|
103
|
+
cli_miami_instruction: {
|
104
|
+
color: :green,
|
105
|
+
style: :bold
|
106
|
+
},
|
107
|
+
cli_miami_instruction_sub: {
|
108
|
+
indent: 2,
|
109
|
+
color: :green
|
110
|
+
},
|
111
|
+
cli_miami_success: {
|
112
|
+
color: :green,
|
113
|
+
style: [:bold, :underline]
|
114
|
+
},
|
115
|
+
cli_miami_update: {
|
116
|
+
indent: 2,
|
117
|
+
color: :cyan
|
118
|
+
}
|
119
|
+
}
|
120
|
+
# rubocop:enable Style/ClassVars
|
121
|
+
|
122
|
+
# Create a new custom preset
|
123
|
+
#
|
124
|
+
def self.set_preset type, options
|
125
|
+
raise ArgumentError, 'Preset must be a hash of options' unless options.is_a? Hash
|
126
|
+
|
127
|
+
# extend preset if it exists
|
128
|
+
extend_preset = options.delete(:preset)
|
129
|
+
if extend_preset && @@presets[extend_preset.to_sym]
|
130
|
+
extend_preset_options = @@presets[extend_preset.to_sym]
|
131
|
+
options = extend_preset_options.merge! options
|
132
|
+
end
|
133
|
+
|
134
|
+
# set options to global var
|
135
|
+
@@presets[type] = options
|
136
|
+
end
|
137
|
+
|
138
|
+
# getter for the presets
|
139
|
+
#
|
140
|
+
def self.presets
|
141
|
+
@@presets
|
142
|
+
end
|
143
|
+
|
144
|
+
# build an options hash from preset options and/or additional options
|
145
|
+
#
|
146
|
+
# build shared validation hash object
|
147
|
+
#
|
148
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
149
|
+
def self.get_options user_options = {}
|
150
|
+
# lookup preset if passed in as a symbol, or as a :preset key in an options hash
|
151
|
+
if user_options.is_a? Hash
|
152
|
+
user_options.deep_symbolize_keys!
|
153
|
+
options = @@presets[user_options.delete(:preset)] || {}
|
154
|
+
options.deep_merge! user_options
|
155
|
+
else
|
156
|
+
options = @@presets[user_options.to_sym] || {}
|
157
|
+
end
|
158
|
+
|
159
|
+
# make sure all keys are symbols
|
160
|
+
options.deep_symbolize_keys!
|
161
|
+
|
162
|
+
# set defaults
|
163
|
+
options.reverse_merge!(
|
164
|
+
description: nil,
|
165
|
+
max: Float::INFINITY,
|
166
|
+
min: options.delete(:required) ? 1 : 0,
|
167
|
+
newline: true,
|
168
|
+
justify: :left,
|
169
|
+
padding: 0,
|
170
|
+
preset: nil,
|
171
|
+
regexp: //,
|
172
|
+
style: [],
|
173
|
+
type: :string
|
174
|
+
)
|
175
|
+
|
176
|
+
# prepare style array
|
177
|
+
options[:style] = [options[:style]].flatten
|
178
|
+
|
179
|
+
# lookup type in type map
|
180
|
+
options[:type] = TYPE_MAP[options[:type].to_sym] || :string
|
181
|
+
|
182
|
+
# convert range to min/max values
|
183
|
+
range = options.delete(:range)
|
184
|
+
if range
|
185
|
+
options[:min] = range.min
|
186
|
+
options[:max] = range.max
|
187
|
+
end
|
188
|
+
|
189
|
+
# convert length to min/max values
|
190
|
+
length = options.delete(:length)
|
191
|
+
if length
|
192
|
+
options[:min] = options[:max] = length
|
193
|
+
end
|
194
|
+
|
195
|
+
# if value options are set, apply the same treatment to them
|
196
|
+
value_options = options[:value_options]
|
197
|
+
if value_options
|
198
|
+
options[:value_options] = get_options value_options
|
199
|
+
options[:value_options][:description] = options[:description]
|
200
|
+
end
|
9
201
|
|
10
|
-
|
11
|
-
|
12
|
-
|
202
|
+
options
|
203
|
+
end
|
204
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
205
|
+
end
|
data/spec/fixtures/i18n.yml
CHANGED
@@ -35,6 +35,12 @@ describe CliMiami::A do
|
|
35
35
|
expect(ask.value).to eq %w(foo bar)
|
36
36
|
end
|
37
37
|
|
38
|
+
it 'should allow user to remove entered values' do
|
39
|
+
allow($stdin).to receive(:gets).and_return 'foo', 'bar', 'foo', ''
|
40
|
+
ask = CliMiami::A.sk @q, type: :array
|
41
|
+
expect(ask.value).to eq %w(bar)
|
42
|
+
end
|
43
|
+
|
38
44
|
it 'should prevent user from entering less than the minimum' do
|
39
45
|
allow($stdin).to receive(:gets).and_return 'foo', '', 'bar', ''
|
40
46
|
ask = CliMiami::A.sk @q, type: :array, min: 2
|
@@ -48,7 +54,57 @@ describe CliMiami::A do
|
|
48
54
|
end
|
49
55
|
end
|
50
56
|
|
57
|
+
describe 'MULTIPLE_CHOICE' do
|
58
|
+
it 'should allow a user to remove selections' do
|
59
|
+
allow($stdin).to receive(:gets).and_return '1', '2', '1', ''
|
60
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, choices: ['option 1', 'option 2', 'option 3']
|
61
|
+
expect(ask.value).to eq ['option 2']
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should not allow a user to select less than the min amount of choices' do
|
65
|
+
allow($stdin).to receive(:gets).and_return '2', '', '1', ''
|
66
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, min: 2, choices: ['option 1', 'option 2', 'option 3']
|
67
|
+
expect(ask.value).to eq ['option 2', 'option 1']
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should not allow a user to select more than the max amount of choices' do
|
71
|
+
allow($stdin).to receive(:gets).and_return '2', '1'
|
72
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, max: 2, choices: ['option 1', 'option 2', 'option 3']
|
73
|
+
expect(ask.value).to eq ['option 2', 'option 1']
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should not allow a user to select an invalid choice' do
|
77
|
+
allow($stdin).to receive(:gets).and_return '4', '-4', '2'
|
78
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, max: 1, choices: ['option 1', 'option 2', 'option 3']
|
79
|
+
expect(ask.value).to eq ['option 2']
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should allow a user to select an amount between the min and max' do
|
83
|
+
allow($stdin).to receive(:gets).and_return '3', '1', ''
|
84
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, min: 1, max: 3, choices: ['option 1', 'option 2', 'option 3']
|
85
|
+
expect(ask.value).to eq ['option 3', 'option 1']
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should allow a user to select all the options' do
|
89
|
+
allow($stdin).to receive(:gets).and_return '3', '1', '2'
|
90
|
+
ask = CliMiami::A.sk @q, type: :multiple_choice, choices: ['option 1', 'option 2', 'option 3']
|
91
|
+
expect(ask.value).to eq ['option 3', 'option 1', 'option 2']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
51
95
|
describe 'HASH' do
|
96
|
+
it 'should allow user to overwrite keys' do
|
97
|
+
allow($stdin).to receive(:gets).and_return 'foo1', '1', 'bar1', '2', 'foo1', '3', ''
|
98
|
+
ask = CliMiami::A.sk @q, type: :hash
|
99
|
+
expect(ask.value).to eq(foo1: '3', bar1: '2')
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should allow user to remove keys' do
|
103
|
+
allow($stdin).to receive(:gets).and_return 'foo1', '1', 'bar1', '2', 'foo1', '', ''
|
104
|
+
ask = CliMiami::A.sk @q, type: :hash
|
105
|
+
expect(ask.value).to eq(bar1: '2')
|
106
|
+
end
|
107
|
+
|
52
108
|
it 'should allow user to enter keys & values until they enter an empty key string' do
|
53
109
|
allow($stdin).to receive(:gets).and_return 'foo1', '1', 'bar1', '2', ''
|
54
110
|
ask = CliMiami::A.sk @q, type: :hash
|
@@ -73,6 +129,12 @@ describe CliMiami::A do
|
|
73
129
|
ask = CliMiami::A.sk @q, type: :hash, keys: [:foo4]
|
74
130
|
expect(ask.value).to eq(foo4: '1', bar4: '2')
|
75
131
|
end
|
132
|
+
|
133
|
+
it 'should not allow user to delete required keys' do
|
134
|
+
allow($stdin).to receive(:gets).and_return 'foo1', 'foo', '', 'bar', 'bar1', ''
|
135
|
+
ask = CliMiami::A.sk @q, type: :hash, keys: [:foo]
|
136
|
+
expect(ask.value).to eq(foo: 'foo1', bar: 'bar1')
|
137
|
+
end
|
76
138
|
end
|
77
139
|
end
|
78
140
|
|
@@ -27,18 +27,18 @@ describe CliMiami::Validation do
|
|
27
27
|
it_behaves_like 'an invalid type', :file, File.join(Dir.pwd, 'foo')
|
28
28
|
end
|
29
29
|
|
30
|
-
describe 'FLOAT' do
|
31
|
-
it_behaves_like 'a valid type', :float, '2.0', 2.0
|
32
|
-
it_behaves_like 'a valid type', :float, '3', 3.0
|
33
|
-
it_behaves_like 'an invalid type', :float, 'foo'
|
34
|
-
end
|
35
|
-
|
36
30
|
describe 'FIXNUM' do
|
37
31
|
it_behaves_like 'a valid type', :fixnum, '3', 3
|
38
32
|
it_behaves_like 'a valid type', :fixnum, '3.8', 3
|
39
33
|
it_behaves_like 'an invalid type', :fixnum, 'foo'
|
40
34
|
end
|
41
35
|
|
36
|
+
describe 'FLOAT' do
|
37
|
+
it_behaves_like 'a valid type', :float, '2.0', 2.0
|
38
|
+
it_behaves_like 'a valid type', :float, '3', 3.0
|
39
|
+
it_behaves_like 'an invalid type', :float, 'foo'
|
40
|
+
end
|
41
|
+
|
42
42
|
describe 'RANGE' do
|
43
43
|
it_behaves_like 'a valid type', :range, '2..4', (2..4)
|
44
44
|
it_behaves_like 'an invalid type', :range, 'foo'
|
data/spec/lib/cli_miami_spec.rb
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
describe CliMiami do
|
2
|
-
|
2
|
+
describe String do
|
3
|
+
before do
|
4
|
+
CliMiami.set_preset :string_spec,
|
5
|
+
color: :green,
|
6
|
+
bgcolor: :blue
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should create methods for calling presets' do
|
10
|
+
expect($stdout).to receive(:puts).with "\e[44m\e[32mSTRING!\e[0m\e[0m"
|
11
|
+
'STRING!'.cli_miami_string_spec
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should extend presets' do
|
15
|
+
expect($stdout).to receive(:puts).with "\e[43m\e[32mSTRING!\e[0m\e[0m"
|
16
|
+
'STRING!'.cli_miami_string_spec bgcolor: :yellow
|
17
|
+
end
|
18
|
+
end
|
3
19
|
|
4
20
|
describe '.set_preset' do
|
5
21
|
it 'should raise an error if invalid options' do
|
@@ -16,15 +32,15 @@ describe CliMiami do
|
|
16
32
|
it 'should extend an existing preset' do
|
17
33
|
subject.set_preset :foo,
|
18
34
|
color: :red,
|
19
|
-
|
35
|
+
bgcolor: :blue
|
20
36
|
|
21
37
|
subject.set_preset :bar,
|
22
38
|
preset: :foo,
|
23
|
-
|
39
|
+
bgcolor: :green
|
24
40
|
|
25
41
|
expect(subject.class_variable_get(:@@presets)[:bar]).to eq(
|
26
42
|
color: :red,
|
27
|
-
|
43
|
+
bgcolor: :green
|
28
44
|
)
|
29
45
|
end
|
30
46
|
end
|
@@ -33,7 +49,7 @@ describe CliMiami do
|
|
33
49
|
before do
|
34
50
|
subject.set_preset :foo,
|
35
51
|
color: 'red',
|
36
|
-
|
52
|
+
bgcolor: 'blue'
|
37
53
|
end
|
38
54
|
|
39
55
|
it 'should handle `required`' do
|
@@ -49,7 +65,7 @@ describe CliMiami do
|
|
49
65
|
it 'should return the preset options' do
|
50
66
|
expect(subject.get_options(:foo)).to include(
|
51
67
|
color: 'red',
|
52
|
-
|
68
|
+
bgcolor: 'blue'
|
53
69
|
)
|
54
70
|
end
|
55
71
|
end
|
@@ -66,22 +82,22 @@ describe CliMiami do
|
|
66
82
|
it 'should return the preset options' do
|
67
83
|
expect(subject.get_options(preset: :foo)).to include(
|
68
84
|
color: 'red',
|
69
|
-
|
85
|
+
bgcolor: 'blue'
|
70
86
|
)
|
71
87
|
end
|
72
88
|
|
73
89
|
it 'should extend preset options with additional passed options' do
|
74
90
|
expect(subject.get_options(preset: :foo, style: 'bold')).to include(
|
75
91
|
color: 'red',
|
76
|
-
|
92
|
+
bgcolor: 'blue',
|
77
93
|
style: ['bold']
|
78
94
|
)
|
79
95
|
end
|
80
96
|
|
81
97
|
it 'should override preset options with additional passed options' do
|
82
|
-
expect(subject.get_options(preset: :foo,
|
98
|
+
expect(subject.get_options(preset: :foo, bgcolor: 'green')).to include(
|
83
99
|
color: 'red',
|
84
|
-
|
100
|
+
bgcolor: 'green'
|
85
101
|
)
|
86
102
|
end
|
87
103
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli_miami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Brewster
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -180,11 +180,10 @@ files:
|
|
180
180
|
- i18n/en.yml
|
181
181
|
- lib/cli_miami.rb
|
182
182
|
- lib/cli_miami/ask.rb
|
183
|
-
- lib/cli_miami/core.rb
|
184
183
|
- lib/cli_miami/error.rb
|
184
|
+
- lib/cli_miami/global.rb
|
185
185
|
- lib/cli_miami/say.rb
|
186
186
|
- lib/cli_miami/validation.rb
|
187
|
-
- lib/namespaced.rb
|
188
187
|
- spec/fixtures/i18n.yml
|
189
188
|
- spec/lib/cli_miami/ask_spec.rb
|
190
189
|
- spec/lib/cli_miami/error_spec.rb
|
data/lib/cli_miami/core.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# 3rd party libraries & configuration
|
3
|
-
#
|
4
|
-
require 'active_support/core_ext/hash/conversions'
|
5
|
-
require 'active_support/core_ext/hash/keys'
|
6
|
-
require 'active_support/core_ext/string/inflections'
|
7
|
-
require 'i18n'
|
8
|
-
require 'readline'
|
9
|
-
|
10
|
-
# core object overrides
|
11
|
-
#
|
12
|
-
class Hash
|
13
|
-
def to_s
|
14
|
-
map do |k, v|
|
15
|
-
"#{k}: #{v}"
|
16
|
-
end.to_sentence
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# i18n
|
21
|
-
#
|
22
|
-
# load yml locales included in the cli miami gem
|
23
|
-
# [PATH TO GEMS]/cli_miami/i18n/en.yml
|
24
|
-
I18n.load_path += Dir["#{File.dirname(__FILE__)}/../i18n/*.yml"]
|
25
|
-
# load locale in current directory named `i18n.yml`
|
26
|
-
# ./i18n.yml
|
27
|
-
I18n.load_path += Dir['./i18n.yml']
|
28
|
-
# load locales in the folder `i18n` in the current directory
|
29
|
-
# ./i18n/en.yml
|
30
|
-
I18n.load_path += Dir['./i18n/*.yml']
|
31
|
-
|
32
|
-
# readline
|
33
|
-
#
|
34
|
-
Readline.completion_append_character = '/'
|
35
|
-
|
36
|
-
# creates Boolean type that true/false inherit
|
37
|
-
# this allows true/false to respond to a single Boolean class to check for
|
38
|
-
#
|
39
|
-
# rubocop:disable Style/Documentation
|
40
|
-
module Boolean; end
|
41
|
-
class TrueClass; include Boolean; end
|
42
|
-
class FalseClass; include Boolean; end
|
43
|
-
# rubocop:enable Style/Documentation
|
44
|
-
|
45
|
-
# global configuration and setup
|
46
|
-
#
|
47
|
-
module CliMiami::Core
|
48
|
-
BOOLEAN_TRUE_VALUES = %w(true t yes y).freeze
|
49
|
-
BOOLEAN_FALSE_VALUES = %w(false f no n).freeze
|
50
|
-
|
51
|
-
# keys: type values allowed through the API
|
52
|
-
# values: type used to validate internally in the Validation class
|
53
|
-
TYPE_MAP = {
|
54
|
-
array: :array,
|
55
|
-
boolean: :boolean,
|
56
|
-
dir: :file,
|
57
|
-
directory: :file,
|
58
|
-
file: :file,
|
59
|
-
fixnum: :fixnum,
|
60
|
-
float: :float,
|
61
|
-
folder: :file,
|
62
|
-
hash: :hash,
|
63
|
-
integer: :fixnum,
|
64
|
-
list: :array,
|
65
|
-
number: :fixnum,
|
66
|
-
object: :hash,
|
67
|
-
path: :file,
|
68
|
-
range: :range,
|
69
|
-
string: :string,
|
70
|
-
symbol: :symbol
|
71
|
-
}.freeze
|
72
|
-
|
73
|
-
# default presets
|
74
|
-
# rubocop:disable Style/ClassVars
|
75
|
-
@@presets = {
|
76
|
-
cli_miami_fail: {
|
77
|
-
color: :red
|
78
|
-
},
|
79
|
-
cli_miami_success: {
|
80
|
-
color: :green
|
81
|
-
}
|
82
|
-
}
|
83
|
-
# rubocop:enable Style/ClassVars
|
84
|
-
end
|
data/lib/namespaced.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
# root namespace class
|
2
|
-
#
|
3
|
-
class CliMiami
|
4
|
-
# require & include core first
|
5
|
-
require 'cli_miami/core'
|
6
|
-
include CliMiami::Core
|
7
|
-
|
8
|
-
# CliMiami library
|
9
|
-
require 'cli_miami/error'
|
10
|
-
require 'cli_miami/validation'
|
11
|
-
require 'cli_miami/ask'
|
12
|
-
require 'cli_miami/say'
|
13
|
-
|
14
|
-
# Create a new custom preset
|
15
|
-
def self.set_preset type, options
|
16
|
-
raise ArgumentError, 'Preset must be a hash of options' unless options.is_a? Hash
|
17
|
-
|
18
|
-
# extend preset if it exists
|
19
|
-
extend_preset = options.delete(:preset)
|
20
|
-
if extend_preset && @@presets[extend_preset.to_sym]
|
21
|
-
extend_preset_options = @@presets[extend_preset.to_sym]
|
22
|
-
options = extend_preset_options.merge! options
|
23
|
-
end
|
24
|
-
|
25
|
-
# set options to global var
|
26
|
-
@@presets[type] = options
|
27
|
-
end
|
28
|
-
|
29
|
-
# build an options hash from preset options and/or additional options
|
30
|
-
#
|
31
|
-
# build shared validation hash object
|
32
|
-
#
|
33
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
34
|
-
def self.get_options user_options = {}
|
35
|
-
# lookup preset if passed in as a symbol, or as a :preset key in an options hash
|
36
|
-
if user_options.is_a? Hash
|
37
|
-
user_options.deep_symbolize_keys!
|
38
|
-
options = @@presets[user_options.delete(:preset)] || {}
|
39
|
-
options.deep_merge! user_options
|
40
|
-
else
|
41
|
-
options = @@presets[user_options.to_sym] || {}
|
42
|
-
end
|
43
|
-
|
44
|
-
# make sure all keys are symbols
|
45
|
-
options.deep_symbolize_keys!
|
46
|
-
|
47
|
-
# set defaults
|
48
|
-
options.reverse_merge!(
|
49
|
-
description: I18n.t('cli_miami.core.no_description'),
|
50
|
-
max: Float::INFINITY,
|
51
|
-
min: options.delete(:required) ? 1 : 0,
|
52
|
-
newline: true,
|
53
|
-
justify: :left,
|
54
|
-
padding: 0,
|
55
|
-
regexp: //,
|
56
|
-
style: [],
|
57
|
-
type: :string
|
58
|
-
)
|
59
|
-
|
60
|
-
# prepare style array
|
61
|
-
options[:style] = [options[:style]].flatten
|
62
|
-
|
63
|
-
# lookup type in type map
|
64
|
-
options[:type] = TYPE_MAP[options[:type].to_sym] || :string
|
65
|
-
|
66
|
-
# convert range to min/max values
|
67
|
-
range = options.delete(:range)
|
68
|
-
if range
|
69
|
-
options[:min] = range.min
|
70
|
-
options[:max] = range.max
|
71
|
-
end
|
72
|
-
|
73
|
-
# convert length to min/max values
|
74
|
-
length = options.delete(:length)
|
75
|
-
if length
|
76
|
-
options[:min] = options[:max] = length
|
77
|
-
end
|
78
|
-
|
79
|
-
# if value options are set, apply the same treatment to them
|
80
|
-
value_options = options[:value_options]
|
81
|
-
if value_options
|
82
|
-
options[:value_options] = get_options value_options
|
83
|
-
options[:value_options][:description] = options[:description]
|
84
|
-
end
|
85
|
-
|
86
|
-
options
|
87
|
-
end
|
88
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
89
|
-
end
|