cliutils 1.1.1 → 1.2.0
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/Gemfile.lock +1 -1
- data/HISTORY.md +6 -0
- data/README.md +150 -44
- data/lib/cliutils/prefs/pref-behavior.rb +14 -0
- data/lib/cliutils/prefs/pref-validation.rb +64 -0
- data/lib/cliutils/prefs/pref.rb +142 -0
- data/lib/cliutils/prefs.rb +45 -46
- data/lib/cliutils/version.rb +1 -1
- data/lib/cliutils.rb +1 -0
- data/res/readme-images/prefs-ask-behaviors.png +0 -0
- data/res/readme-images/prefs-ask-options.png +0 -0
- data/res/readme-images/prefs-ask-prereqs.png +0 -0
- data/res/readme-images/prefs-ask-validators.png +0 -0
- data/res/readme-images/prefs-ask.png +0 -0
- data/test/prefs_test.rb +19 -8
- data/test/test_files/prefstest.yaml +5 -35
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f244aff7b8639cf70d06eee8bef9f69d1ccaced
|
4
|
+
data.tar.gz: 16845634a0d353037b7c60f200bc76788abc73b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78b54b0002d6c07b1189983782b46462c2db1225b573d5924af0d53059f2a01925421c0cc2160b4410021c04d6985f739d2f695c02b6f744d5a1044c2f693807
|
7
|
+
data.tar.gz: 769aaa4b4026175ef9c4bcce2aa373639472759f5fcad62df3a90d4169316e6cdccf3f3ddbcde9b41b8d1b923942365716535aefb1edc521c0d19984fe49590f
|
data/Gemfile.lock
CHANGED
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -169,6 +169,8 @@ messenger.info("You answered: #{ p }")
|
|
169
169
|
```
|
170
170
|

|
171
171
|
|
172
|
+
When you pass a default to `messenging.prompt`, hitting `Enter` (i.e., leaving the prompt blank) will return the value of the default.
|
173
|
+
|
172
174
|
### Logging
|
173
175
|
|
174
176
|
Often, it's desirable to log messages as they appear to your user. `messenging` makes this a breeze by allowing you to attach and detach Logger instances at will.
|
@@ -180,7 +182,7 @@ messenger.info('This should only appear in STDOUT.')
|
|
180
182
|
|
181
183
|
# messenger.attach takes a Hash of string/symbol keys
|
182
184
|
# and Logger values (so you can refer to them later on).
|
183
|
-
messenger.attach(
|
185
|
+
messenger.attach(MY_FILE_LOGGER: Logger.new('file.txt'))
|
184
186
|
|
185
187
|
messenger.warn('This warning should appear in STDOUT and file.txt')
|
186
188
|
messenger.error('This error should appear in STDOUT and file.txt')
|
@@ -188,7 +190,7 @@ messenger.debug('This debug message should only appear in file.txt')
|
|
188
190
|
|
189
191
|
# messenger.detach takes the string/symbol key
|
190
192
|
# defined earlier.
|
191
|
-
messenger.detach(:
|
193
|
+
messenger.detach(:MY_FILE_LOGGER)
|
192
194
|
|
193
195
|
messenger.section('This section message should appear only in STDOUT')
|
194
196
|
```
|
@@ -209,7 +211,7 @@ Since you can attach Logger objects, each can have it's own format and severity
|
|
209
211
|
|
210
212
|
## Configuration
|
211
213
|
|
212
|
-
CLIUtils offers
|
214
|
+
CLIUtils offers a `Configurator` class and a `Configuration` module (which provides access to a shared instance of `Configurator`) that make managing a user's configuration parameters easy. Mix it in!
|
213
215
|
|
214
216
|
```Ruby
|
215
217
|
include CLIUtils::Configuration
|
@@ -263,49 +265,31 @@ user_data:
|
|
263
265
|
|
264
266
|
Many times, CLI apps need to ask their users some questions, collect the feedback, validate it, and store it. CLIUtils makes this a breeze via the `Prefs` class.
|
265
267
|
|
268
|
+
### Basic Schema
|
269
|
+
|
266
270
|
`Prefs` can load preferences information from either a YAML file (via a filepath) or from an array of preferences. In either case, the schema is the same; each prompt includes the following:
|
267
271
|
|
268
|
-
* prompt (**required**): the string to prompt your user with
|
269
|
-
* default (*optional*): an optional default to offer
|
270
|
-
*
|
271
|
-
*
|
272
|
-
* options (*optional*): an optional array of values; the user's choice must be in this array
|
273
|
-
* requirements (*optional*): an optional list of key/value pairs that must exist for this preference to be displayed
|
272
|
+
* `prompt` (**required**): the string to prompt your user with
|
273
|
+
* `default` (*optional*): an optional default to offer
|
274
|
+
* `config_key` (**required**): the Configurator key that this preference will use
|
275
|
+
* `config_section` (**required**): the Configurator section that this preference applies to
|
274
276
|
|
275
277
|
Here's an example YAML preferences file.
|
276
278
|
|
277
279
|
```YAML
|
278
280
|
prompts:
|
279
|
-
- prompt: What is
|
280
|
-
default:
|
281
|
-
|
282
|
-
|
283
|
-
- prompt: What is
|
284
|
-
default:
|
285
|
-
|
286
|
-
|
287
|
-
- prompt:
|
288
|
-
default:
|
289
|
-
|
290
|
-
|
291
|
-
- prompt: Do you use password or key authentication?
|
292
|
-
default: password
|
293
|
-
key: auth_method
|
294
|
-
section: ssh_info
|
295
|
-
options: ['password', 'key']
|
296
|
-
- prompt: Where is your key located?
|
297
|
-
default: ~/.ssh
|
298
|
-
key: key_location
|
299
|
-
section: ssh_info
|
300
|
-
requirements:
|
301
|
-
- key: auth_method
|
302
|
-
value: key
|
303
|
-
- prompt: What is your password?
|
304
|
-
key: password
|
305
|
-
section: ssh_info
|
306
|
-
requirements:
|
307
|
-
- key: auth_method
|
308
|
-
value: password
|
281
|
+
- prompt: What is your name?
|
282
|
+
default: Bob Cobb
|
283
|
+
config_key: name
|
284
|
+
config_section: personal_info
|
285
|
+
- prompt: What is your age?
|
286
|
+
default: 45
|
287
|
+
config_key: age
|
288
|
+
config_section: personal_info
|
289
|
+
- prompt: Batman or Superman?
|
290
|
+
default: Batman
|
291
|
+
config_key: superhero
|
292
|
+
config_section: personal_info
|
309
293
|
```
|
310
294
|
|
311
295
|
Assuming the above, `Prefs` is instantiated like so:
|
@@ -321,13 +305,137 @@ prefs.ask
|
|
321
305
|
```
|
322
306
|

|
323
307
|
|
308
|
+
### Prerequisites
|
309
|
+
|
310
|
+
Sometimes, you need to answer certain prompts before others become relevant. `Prefs` allows this via a `prereqs` key, which can contain multiple already-answered key/value pairs to check for. For instance, imagine we want to drill into a user's superhero preference a bit further:
|
311
|
+
|
312
|
+
```YAML
|
313
|
+
prompts:
|
314
|
+
- prompt: Batman or Superman?
|
315
|
+
default: Batman
|
316
|
+
config_key: superhero
|
317
|
+
config_section: personal_info
|
318
|
+
- prompt: Do you feel smart for preferring Batman?
|
319
|
+
default: Y
|
320
|
+
config_key: batman_answer
|
321
|
+
config_section: personal_info
|
322
|
+
prereqs:
|
323
|
+
- config_key: superhero
|
324
|
+
config_value: Batman
|
325
|
+
- prompt: Why do you prefer Superman?!
|
326
|
+
default: No clue
|
327
|
+
config_key: superman_answer
|
328
|
+
config_section: personal_info
|
329
|
+
prereqs:
|
330
|
+
- config_key: superhero
|
331
|
+
config_value: Superman
|
332
|
+
```
|
333
|
+
|
334
|
+
`prereqs` checks for already-answered preferences (based on a Configurator key and value); assuming everything checks out, the subsequent preferences are collected:
|
335
|
+
|
336
|
+
```Ruby
|
337
|
+
prefs.ask
|
338
|
+
```
|
339
|
+

|
340
|
+
|
341
|
+
Be careful tht you don't define any circular prerequisities (e.g., A requires B and B requires A). In that case, neither preference will be collected.
|
342
|
+
|
343
|
+
### Options
|
344
|
+
|
345
|
+
What if you want to limit a preference to a certain set of options? Easy! Imagine we want to expand the previous example and force the user to choose either "Batman" or "Superman":
|
346
|
+
|
347
|
+
```YAML
|
348
|
+
prompts:
|
349
|
+
- prompt: Batman or Superman?
|
350
|
+
default: Batman
|
351
|
+
config_key: superhero
|
352
|
+
config_section: personal_info
|
353
|
+
options: ['Batman', 'Superman']
|
354
|
+
- prompt: Do you feel smart for preferring Batman?
|
355
|
+
default: Y
|
356
|
+
config_key: batman_answer
|
357
|
+
config_section: personal_info
|
358
|
+
prereqs:
|
359
|
+
- config_key: superhero
|
360
|
+
config_value: Batman
|
361
|
+
- prompt: Why do you prefer Superman?!
|
362
|
+
default: No clue
|
363
|
+
config_key: superman_answer
|
364
|
+
config_section: personal_info
|
365
|
+
prereqs:
|
366
|
+
- config_key: superhero
|
367
|
+
config_value: Superman
|
368
|
+
```
|
369
|
+
|
370
|
+
Once in place:
|
371
|
+
|
372
|
+
```Ruby
|
373
|
+
prefs.ask
|
374
|
+
```
|
375
|
+

|
376
|
+
|
377
|
+
### Validators
|
378
|
+
|
379
|
+
"But," you say, "I want to ensure that my user gives answers that conform to certain specifications!" Not a problem, dear user, `Prefs` has you covered:
|
380
|
+
|
381
|
+
```YAML
|
382
|
+
prompts:
|
383
|
+
- prompt: What is your name?
|
384
|
+
config_key: name
|
385
|
+
config_section: personal_info
|
386
|
+
validators:
|
387
|
+
- alphabetic
|
388
|
+
- prompt: How old are you?
|
389
|
+
config_key: age
|
390
|
+
config_section: personal_info
|
391
|
+
validators:
|
392
|
+
- numeric
|
393
|
+
```
|
394
|
+
|
395
|
+
```Ruby
|
396
|
+
prefs.ask
|
397
|
+
```
|
398
|
+

|
399
|
+
|
400
|
+
`Prefs` currently supports these validators:
|
401
|
+
|
402
|
+
* `alphabetic`: must be made up of letters and spaces
|
403
|
+
* `alphanumeric`: must be made up of letters, numbers, and spaces
|
404
|
+
* `date`: must be a parsable date (e.g., 2014-04-03)
|
405
|
+
* `non_nil`: must be a non-nil value
|
406
|
+
* `numeric`: must be made up of numbers
|
407
|
+
* `url`: must be a fully-qualified URL
|
408
|
+
|
409
|
+
### Behaviors
|
410
|
+
|
411
|
+
Finally, a common desire might be to modify the user's answer in some way:
|
412
|
+
|
413
|
+
```YAML
|
414
|
+
prompts:
|
415
|
+
- prompt: Where is your SSH public key located?
|
416
|
+
config_key: pub_key
|
417
|
+
config_section: personal_info
|
418
|
+
behaviors:
|
419
|
+
- local_filepath
|
420
|
+
```
|
421
|
+
|
422
|
+
This will expand the user's answer as a filepath (e.g., `~/.ssh` will get saved as `/Users/bachya/.ssh/`).
|
423
|
+
|
424
|
+
`Prefs` currently supports these behaviors:
|
425
|
+
|
426
|
+
* `local_filepath`: runs File.expand_path on the answer
|
427
|
+
|
428
|
+
### Adding Pref Responses to a Configurator
|
429
|
+
|
324
430
|
Once the user has answered all the preference prompts, you can fold those answers back into a Configurator using the `ingest` method:
|
325
431
|
|
326
432
|
```Ruby
|
327
|
-
configuration.
|
433
|
+
configuration.ingest_prefs(prefs)
|
328
434
|
configuration.save
|
329
435
|
```
|
330
436
|
|
437
|
+
### Using Configurator Values as Defaults
|
438
|
+
|
331
439
|
Note that you can also initialize a `Prefs` object with a Configurator:
|
332
440
|
|
333
441
|
```Ruby
|
@@ -336,10 +444,6 @@ prefs = CLIUtils::Prefs.new('path/to/yaml/file', my_configurator)
|
|
336
444
|
|
337
445
|
In this case, `Prefs` will look to see if any values already exist for a specific prompt; if so, that value will be used as the default, rather than the default specified in the YAML.
|
338
446
|
|
339
|
-
### Why a Prefs Class?
|
340
|
-
|
341
|
-
I've written apps that need to request user input at various times for multiple different things; as such, I thought it'd be easier to have those scenarios chunked up. You can always wrap `Prefs` into a module singleton if you wish.
|
342
|
-
|
343
447
|
# Known Issues
|
344
448
|
|
345
449
|
* LoggerDelegator doesn't currently know what to do with `messenger.prompt`, so you'll have to manually log a `debug` message if you want that information logged.
|
@@ -348,6 +452,8 @@ I've written apps that need to request user input at various times for multiple
|
|
348
452
|
|
349
453
|
To report bugs with or suggest features/changes for CLIUtils, please use the [Issues Page](http://github.com/bachya/cli-utils/issues).
|
350
454
|
|
455
|
+
* Trello Board: [https://trello.com/b/qXs7Yeir/cliutils](https://trello.com/b/qXs7Yeir/cliutils "CLIUtils on Trello")
|
456
|
+
|
351
457
|
# Contributing
|
352
458
|
|
353
459
|
Contributions are welcome and encouraged. To contribute:
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CLIUtils
|
2
|
+
# PrefBehavior Module
|
3
|
+
# Behaviors that should be applied to a Pref's
|
4
|
+
# final value
|
5
|
+
module PrefBehavior
|
6
|
+
# Expands the passed text (assumes it
|
7
|
+
# is a filepath).
|
8
|
+
# @param [String] text The text to evaluate
|
9
|
+
# @return [String]
|
10
|
+
def self.local_filepath(text)
|
11
|
+
File.expand_path(text)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module CLIUtils
|
4
|
+
# PrefValidation Module
|
5
|
+
# Validation rules that can be applied to a Pref.
|
6
|
+
module PrefValidation
|
7
|
+
Validator = Struct.new(:code, :message)
|
8
|
+
|
9
|
+
# Validates that a value is only letters.
|
10
|
+
# @param [String] text The text to inspect
|
11
|
+
# @return [Boolean]
|
12
|
+
def self.alphabetic(text)
|
13
|
+
m = "Response is not alphabetic: #{ text }"
|
14
|
+
c = text.to_s =~ /\A[A-Za-z\s]+\z/
|
15
|
+
Validator.new(c, m)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Validates that a value is only letters and numbers.
|
19
|
+
# @param [String] text The text to inspect
|
20
|
+
# @return [Boolean]
|
21
|
+
def self.alphanumeric(text)
|
22
|
+
m = "Response is not alphanumeric: #{ text }"
|
23
|
+
c = text.to_s =~ /\A[A-Za-z0-9\s]+\z/
|
24
|
+
Validator.new(c, m)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validates that a value is a date.
|
28
|
+
# @param [String] text The text to inspect
|
29
|
+
# @return [Boolean]
|
30
|
+
def self.date(text)
|
31
|
+
m = "Response is not a date: #{ text }"
|
32
|
+
c = !(Date.parse(text) rescue nil).nil?
|
33
|
+
Validator.new(c, m)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Validates that a value is passed and is not
|
37
|
+
# empty.
|
38
|
+
# @param [String] text The text to inspect
|
39
|
+
# @return [Boolean]
|
40
|
+
def self.non_nil(text)
|
41
|
+
m = "Nil text not allowed"
|
42
|
+
c = !text.nil? && !text.empty?
|
43
|
+
Validator.new(c, m)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validates that a value is some sort of number.
|
47
|
+
# @param [String] text The text to inspect
|
48
|
+
# @return [Boolean]
|
49
|
+
def self.numeric(text)
|
50
|
+
m = "Response is not a number: #{ text }"
|
51
|
+
c = text.to_s =~ /\A[-+]?\d*\.?\d+\z/
|
52
|
+
Validator.new(c, m)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Validates that passed value is a URL.
|
56
|
+
# @param [String] text The text to inspect
|
57
|
+
# @return [Boolean]
|
58
|
+
def self.url(text)
|
59
|
+
m = "Response is not a url: #{ text }"
|
60
|
+
c = text.to_s =~ URI::DEFAULT_PARSER.regexp[:ABS_URI]
|
61
|
+
Validator.new(c, m)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'cliutils/messenging'
|
2
|
+
require 'cliutils/prefs/pref-behavior'
|
3
|
+
require 'cliutils/prefs/pref-validation'
|
4
|
+
|
5
|
+
module CLIUtils
|
6
|
+
# Pref Class
|
7
|
+
# An individual preference
|
8
|
+
class Pref
|
9
|
+
include Messenging
|
10
|
+
|
11
|
+
# Stores the answer to this Pref.
|
12
|
+
# @return [String, Symbol]
|
13
|
+
attr_accessor :answer
|
14
|
+
|
15
|
+
# Stores the behaviors that this Pref conforms to.
|
16
|
+
# @return [Array]
|
17
|
+
attr_accessor :behaviors
|
18
|
+
|
19
|
+
# Stores a key to reference this pref in a Configurator.
|
20
|
+
# @return [String, Symbol]
|
21
|
+
attr_accessor :config_key
|
22
|
+
|
23
|
+
# Stores a Configurator section to stick this Pref under.
|
24
|
+
# @return [String, Symbol]
|
25
|
+
attr_accessor :config_section
|
26
|
+
|
27
|
+
# Stores the default text.
|
28
|
+
# @return [String]
|
29
|
+
attr_accessor :default
|
30
|
+
|
31
|
+
# Stores the last error message.
|
32
|
+
# @return [String]
|
33
|
+
attr_accessor :last_error_message
|
34
|
+
|
35
|
+
# Stores the valid options the user can pick from.
|
36
|
+
# @return [Array]
|
37
|
+
attr_accessor :options
|
38
|
+
|
39
|
+
# Stores the prereqs information.
|
40
|
+
# @return [Array]
|
41
|
+
attr_accessor :prereqs
|
42
|
+
|
43
|
+
# Stores the prompt text.
|
44
|
+
# @return [String]
|
45
|
+
attr_accessor :prompt
|
46
|
+
|
47
|
+
# Stores key/value combinations required to show this Pref.
|
48
|
+
# @return [Hash]
|
49
|
+
attr_accessor :validators
|
50
|
+
|
51
|
+
# Initializes a new Pref via passed-in parameters.
|
52
|
+
# @param [Hash] params Parameters to initialize
|
53
|
+
# @return [void]
|
54
|
+
def initialize(params = {})
|
55
|
+
params.each { |key, value| send("#{ key }=", value) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Custom equality operator for this class.
|
59
|
+
# @param [Pref] other
|
60
|
+
# @return [Boolean]
|
61
|
+
def ==(other)
|
62
|
+
return self.answer == other.answer &&
|
63
|
+
self.behaviors == other.behaviors &&
|
64
|
+
self.config_key == other.config_key &&
|
65
|
+
self.config_section == other.config_section &&
|
66
|
+
self.default == other.default &&
|
67
|
+
self.last_error_message == other.last_error_message &&
|
68
|
+
self.options == other.options &&
|
69
|
+
self.prereqs == other.prereqs &&
|
70
|
+
self.prompt == other.prompt &&
|
71
|
+
self.validators == other.validators
|
72
|
+
end
|
73
|
+
|
74
|
+
# Runs the passed text through this Pref's behaviors.
|
75
|
+
# @param [String] text The text to evaluate
|
76
|
+
# @return [String]
|
77
|
+
def evaluate_behaviors(text)
|
78
|
+
if @behaviors
|
79
|
+
modified_text = text
|
80
|
+
@behaviors.each do |b|
|
81
|
+
if PrefBehavior.respond_to?(b)
|
82
|
+
modified_text = PrefBehavior.send(b, modified_text)
|
83
|
+
else
|
84
|
+
messenger.warn("Skipping undefined Pref behavior: #{ b }")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
modified_text
|
89
|
+
else
|
90
|
+
text
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Validates a text against this pref's options and
|
95
|
+
# validators.
|
96
|
+
# @param [String] text The text to validate
|
97
|
+
# @return [Boolean]
|
98
|
+
def validate(text)
|
99
|
+
_confirm_options(text) &&
|
100
|
+
_confirm_validators(text)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Validates a text against the options for this Pref
|
106
|
+
# @param [String] text The text to validate
|
107
|
+
# @return [Boolean]
|
108
|
+
def _confirm_options(text)
|
109
|
+
ret = true
|
110
|
+
if @options
|
111
|
+
unless @options.include?(text)
|
112
|
+
@last_error_message = "Invalid option chosen (\"#{ text }\"); valid options are: #{ options }"
|
113
|
+
ret = false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
ret
|
118
|
+
end
|
119
|
+
|
120
|
+
# Validates a text against the validators for this Pref
|
121
|
+
# @param [String] text The text to validate
|
122
|
+
# @return [Boolean]
|
123
|
+
def _confirm_validators(text)
|
124
|
+
ret = true
|
125
|
+
if @validators
|
126
|
+
@validators.each do |v|
|
127
|
+
if PrefValidation.respond_to?(v)
|
128
|
+
validator = PrefValidation.send(v, text)
|
129
|
+
unless validator.code
|
130
|
+
@last_error_message = validator.message
|
131
|
+
ret = false
|
132
|
+
end
|
133
|
+
else
|
134
|
+
messenger.warn("Skipping undefined Pref validator: #{ v }")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
ret
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/cliutils/prefs.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
+
require 'cliutils/prefs/pref'
|
1
2
|
require 'cliutils/pretty-io'
|
2
3
|
|
3
4
|
module CLIUtils
|
4
5
|
# Engine to derive preferences from a YAML file, deliver
|
5
6
|
# those to a user via a prompt, and collect the results.
|
6
7
|
class Prefs
|
7
|
-
include
|
8
|
-
# Stores answers to prompt questions.
|
9
|
-
# @return [Array]
|
10
|
-
attr_reader :answers
|
11
|
-
|
8
|
+
include Messenging
|
12
9
|
# Stores the filepath (if it exists) to the prefs file.
|
13
10
|
# @return [String]
|
14
11
|
attr_reader :config_path
|
@@ -18,8 +15,8 @@ module CLIUtils
|
|
18
15
|
attr_reader :configurator
|
19
16
|
|
20
17
|
# Stores answers to prompt questions.
|
21
|
-
# @return [
|
22
|
-
attr_reader :
|
18
|
+
# @return [Array]
|
19
|
+
attr_reader :prefs
|
23
20
|
|
24
21
|
# Reads prompt data from and stores it.
|
25
22
|
# @param [<String, Hash, Array>] data Filepath to YAML, Hash, or Array
|
@@ -28,28 +25,26 @@ module CLIUtils
|
|
28
25
|
def initialize(data, configurator = nil)
|
29
26
|
@answers = []
|
30
27
|
@configurator = configurator
|
31
|
-
@
|
28
|
+
@prefs = []
|
32
29
|
|
33
30
|
case data
|
34
31
|
when String
|
35
32
|
if File.exists?(data)
|
36
33
|
@config_path = data
|
37
|
-
|
38
|
-
|
39
|
-
@prompts.deep_merge!(prompts).deep_symbolize_keys!
|
34
|
+
data = YAML::load_file(data).deep_symbolize_keys
|
35
|
+
@prefs = _generate_prefs(data)
|
40
36
|
else
|
41
|
-
fail "Invalid configuration file: #{ data }"
|
37
|
+
fail "Invalid configuration file: #{ data }"
|
42
38
|
end
|
43
39
|
when Hash
|
44
40
|
@config_path = nil
|
45
|
-
|
46
41
|
data = {:prompts => data} unless data.keys[0] == :prompts
|
47
|
-
|
42
|
+
data.deep_symbolize_keys!
|
43
|
+
@prefs = _generate_prefs(data)
|
48
44
|
when Array
|
49
45
|
@config_path = nil
|
50
|
-
|
51
|
-
|
52
|
-
@prompts.deep_merge!(prompts).deep_symbolize_keys!
|
46
|
+
data = {:prompts => data}.deep_symbolize_keys
|
47
|
+
@prefs = _generate_prefs(data)
|
53
48
|
else
|
54
49
|
fail 'Invalid configuration data'
|
55
50
|
end
|
@@ -57,16 +52,16 @@ module CLIUtils
|
|
57
52
|
|
58
53
|
# Runs through all of the prompt questions and collects
|
59
54
|
# answers from the user. Note that all questions w/o
|
60
|
-
#
|
61
|
-
# complete, questions w/
|
55
|
+
# prerequisites are examined first; once those are
|
56
|
+
# complete, questions w/ prerequisites are examined.
|
62
57
|
# @return [void]
|
63
58
|
def ask
|
64
|
-
@
|
59
|
+
@prefs.reject { |p| p.prereqs }.each do |p|
|
65
60
|
_deliver_prompt(p)
|
66
61
|
end
|
67
|
-
|
68
|
-
@
|
69
|
-
_deliver_prompt(p) if
|
62
|
+
|
63
|
+
@prefs.find_all { |p| p.prereqs }.each do |p|
|
64
|
+
_deliver_prompt(p) if _prereqs_fulfilled?(p)
|
70
65
|
end
|
71
66
|
end
|
72
67
|
|
@@ -77,42 +72,46 @@ module CLIUtils
|
|
77
72
|
# @param [Hash] p The prompt
|
78
73
|
# @return [void]
|
79
74
|
def _deliver_prompt(p)
|
80
|
-
default = p
|
75
|
+
default = p.default
|
81
76
|
|
82
77
|
unless @configurator.nil?
|
83
|
-
unless @configurator.data[p
|
84
|
-
config_val = @configurator.data[p
|
78
|
+
unless @configurator.data[p.section.to_sym].nil?
|
79
|
+
config_val = @configurator.data[p.config_section.to_sym][p.config_key.to_sym]
|
85
80
|
default = config_val unless config_val.nil?
|
86
81
|
end
|
87
82
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
error("Invalid option chosen (\"#{ pref }\"); valid options are: #{ p[:options] }")
|
99
|
-
end
|
83
|
+
|
84
|
+
valid_option_chosen = false
|
85
|
+
until valid_option_chosen
|
86
|
+
response = prompt(p.prompt, default)
|
87
|
+
|
88
|
+
if p.validate(response)
|
89
|
+
valid_option_chosen = true
|
90
|
+
p.answer = p.evaluate_behaviors(response)
|
91
|
+
else
|
92
|
+
messenger.error(p.last_error_message)
|
100
93
|
end
|
101
94
|
end
|
102
|
-
|
103
|
-
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generates an Array of Prefs based on passed
|
98
|
+
# in data.
|
99
|
+
# @param [Hash] pref_data Loaded pref data
|
100
|
+
# @return [Array]
|
101
|
+
def _generate_prefs(pref_data_hash)
|
102
|
+
pref_data_hash[:prompts].map { |p| CLIUtils::Pref.new(p) }
|
104
103
|
end
|
105
104
|
|
106
105
|
# Utility method for determining whether a prompt's
|
107
|
-
#
|
106
|
+
# prerequisites have already been fulfilled.
|
108
107
|
# @param [Hash] p The prompt
|
109
108
|
# @return [void]
|
110
|
-
def
|
109
|
+
def _prereqs_fulfilled?(p)
|
111
110
|
ret = true
|
112
|
-
p
|
113
|
-
a = @
|
114
|
-
answer
|
115
|
-
answer
|
111
|
+
p.prereqs.each do |req|
|
112
|
+
a = @prefs.detect do |answer|
|
113
|
+
answer.config_key == req[:config_key] &&
|
114
|
+
answer.answer == req[:config_value]
|
116
115
|
end
|
117
116
|
ret = false if a.nil?
|
118
117
|
end
|
data/lib/cliutils/version.rb
CHANGED
data/lib/cliutils.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/test/prefs_test.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'cliutils/ext/hash+extensions'
|
2
|
+
require 'cliutils/prefs'
|
3
|
+
require 'cliutils/prefs/pref'
|
2
4
|
require 'test/unit'
|
3
5
|
require 'yaml'
|
4
6
|
|
5
|
-
require File.join(File.dirname(__FILE__), '..', 'lib/cliutils/ext/Hash+Extensions')
|
6
|
-
require File.join(File.dirname(__FILE__), '..', 'lib/cliutils/prefs')
|
7
|
-
|
8
7
|
class TestPrefs < Test::Unit::TestCase
|
9
8
|
def setup
|
10
|
-
@prefs_arr = [{:prompt=>"
|
9
|
+
@prefs_arr = [{:prompt=>"Where is your SSH public key located?", :config_key=>"pub_key", :config_section=>"personal_info", :behaviors=>["local_filepath"]}]
|
10
|
+
@prefs_hash = {:prompts=>@prefs_arr}
|
11
11
|
|
12
12
|
@prefs_filepath = '/tmp/prefstest.yaml'
|
13
13
|
FileUtils.cp(File.join(File.dirname(__FILE__), '..', 'test/test_files/prefstest.yaml'), @prefs_filepath)
|
@@ -19,11 +19,22 @@ class TestPrefs < Test::Unit::TestCase
|
|
19
19
|
|
20
20
|
def test_file_creation
|
21
21
|
p = CLIUtils::Prefs.new(@prefs_filepath)
|
22
|
-
|
22
|
+
prefs = YAML::load_file(@prefs_filepath).deep_symbolize_keys
|
23
|
+
|
24
|
+
assert_equal(prefs[:prompts].map { |p| CLIUtils::Pref.new(p) }, p.prefs)
|
23
25
|
end
|
24
26
|
|
25
|
-
def
|
27
|
+
def test_array_creation
|
26
28
|
p = CLIUtils::Prefs.new(@prefs_arr)
|
27
|
-
|
29
|
+
prefs = @prefs_hash.deep_symbolize_keys
|
30
|
+
|
31
|
+
assert_equal(prefs[:prompts].map { |p| CLIUtils::Pref.new(p) }, p.prefs)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_hash_creation
|
35
|
+
p = CLIUtils::Prefs.new(@prefs_hash)
|
36
|
+
prefs = @prefs_hash.deep_symbolize_keys
|
37
|
+
|
38
|
+
assert_equal(prefs[:prompts].map { |p| CLIUtils::Pref.new(p) }, p.prefs)
|
28
39
|
end
|
29
40
|
end
|
@@ -1,36 +1,6 @@
|
|
1
1
|
prompts:
|
2
|
-
- prompt:
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
key: remote_hostname
|
8
|
-
section: ssh_info
|
9
|
-
- prompt: What is the SSH username of your DD-WRT router?
|
10
|
-
default: root
|
11
|
-
key: username
|
12
|
-
section: ssh_info
|
13
|
-
- prompt: What SSH port does your DD-WRT router use?
|
14
|
-
default: 22
|
15
|
-
key: port
|
16
|
-
section: ssh_info
|
17
|
-
- prompt: Do you use password or key authentication?
|
18
|
-
default: password
|
19
|
-
key: auth_method
|
20
|
-
section: ssh_info
|
21
|
-
options: ['password', 'key']
|
22
|
-
- prompt: Where is your key located?
|
23
|
-
default: ~/.ssh
|
24
|
-
key: key_location
|
25
|
-
section: ssh_info
|
26
|
-
requirements:
|
27
|
-
- key: auth_method
|
28
|
-
value: key
|
29
|
-
- prompt: What is your password?
|
30
|
-
key: password
|
31
|
-
section: ssh_info
|
32
|
-
requirements:
|
33
|
-
- key: auth_method
|
34
|
-
value: password
|
35
|
-
|
36
|
-
|
2
|
+
- prompt: Where is your SSH public key located?
|
3
|
+
config_key: pub_key
|
4
|
+
config_section: personal_info
|
5
|
+
behaviors:
|
6
|
+
- local_filepath
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cliutils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Bach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -93,11 +93,18 @@ files:
|
|
93
93
|
- lib/cliutils/logger-delegator.rb
|
94
94
|
- lib/cliutils/messenging.rb
|
95
95
|
- lib/cliutils/prefs.rb
|
96
|
+
- lib/cliutils/prefs/pref-behavior.rb
|
97
|
+
- lib/cliutils/prefs/pref-validation.rb
|
98
|
+
- lib/cliutils/prefs/pref.rb
|
96
99
|
- lib/cliutils/pretty-io.rb
|
97
100
|
- lib/cliutils/version.rb
|
98
101
|
- res/readme-images/messenger-types-1.png
|
99
102
|
- res/readme-images/messenger-warn.png
|
100
103
|
- res/readme-images/multi-logger.png
|
104
|
+
- res/readme-images/prefs-ask-behaviors.png
|
105
|
+
- res/readme-images/prefs-ask-options.png
|
106
|
+
- res/readme-images/prefs-ask-prereqs.png
|
107
|
+
- res/readme-images/prefs-ask-validators.png
|
101
108
|
- res/readme-images/prefs-ask.png
|
102
109
|
- res/readme-images/prettyio-color-chart.png
|
103
110
|
- res/readme-images/prettyio-gnarly-text.png
|