cliutils 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63361eba2c187fdc7bd5dbff677bf976fbd97559
4
- data.tar.gz: 7e191c796aee68c3fa9a80641bbea6385bd05520
3
+ metadata.gz: 6f244aff7b8639cf70d06eee8bef9f69d1ccaced
4
+ data.tar.gz: 16845634a0d353037b7c60f200bc76788abc73b7
5
5
  SHA512:
6
- metadata.gz: c07545c990b26e7fb5635e60e919ef886dbdb0bf291cae0425cad9d7aba057da4084076e0e1ac645502ff50cf74a19100140f035c337451f3df21b76e7eb18fe
7
- data.tar.gz: c8bf5139e9b1c610e4ff05961ca794c164ed59d2955b04e6ad212e146b6c2c73f9841c2287e9d6c5174276295868e048105cb43aa8dcfef765fe336407542cc1
6
+ metadata.gz: 78b54b0002d6c07b1189983782b46462c2db1225b573d5924af0d53059f2a01925421c0cc2160b4410021c04d6985f739d2f695c02b6f744d5a1044c2f693807
7
+ data.tar.gz: 769aaa4b4026175ef9c4bcce2aa373639472759f5fcad62df3a90d4169316e6cdccf3f3ddbcde9b41b8d1b923942365716535aefb1edc521c0d19984fe49590f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cliutils (1.1.1)
4
+ cliutils (1.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/HISTORY.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 1.1.0 (2014-04-03)
2
+
3
+ * Changed Requirements => Prerequisites in Prefs
4
+ * Added Validators to Prefs
5
+ * Added Behaviors to Prefs
6
+
1
7
  # 1.1.0 (2014-04-01)
2
8
 
3
9
  * Added notice of valid options in Prefs
data/README.md CHANGED
@@ -169,6 +169,8 @@ messenger.info("You answered: #{ p }")
169
169
  ```
170
170
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prompting.png "Prompting")
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(MY\_FILE\_LOGGER: Logger.new('file.txt'))
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(:MY\_FILE\_LOGGER)
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 two "things" -- a `Configurator` class and a `Configuration` module that provides access to a shared instance of `Configurator` -- that make managing a user's configuration parameters easy. Mix it in!
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
- * key (**required**): the key that refers to this preference
271
- * section (**required**): the Configuration section that this preference applies to
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 the hostname of your DD-WRT router?
280
- default: 192.168.1.1
281
- key: hostname
282
- section: ssh_info
283
- - prompt: What is the SSH username of your DD-WRT router?
284
- default: root
285
- key: username
286
- section: ssh_info
287
- - prompt: What SSH port does your DD-WRT router use?
288
- default: 22
289
- key: port
290
- section: ssh_info
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
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask.png "Prefs.ask")
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
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask-prereqs.png "Prerequisities")
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
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask-options.png "Options")
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
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask-validators.png "Validators")
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.ingest(prefs)
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
@@ -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 PrettyIO
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 [Hash]
22
- attr_reader :prompts
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
- @prompts = {}
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
- prompts = YAML::load_file(data)
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
- @prompts.deep_merge!(data).deep_symbolize_keys!
42
+ data.deep_symbolize_keys!
43
+ @prefs = _generate_prefs(data)
48
44
  when Array
49
45
  @config_path = nil
50
-
51
- prompts = {:prompts => data}
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
- # requirements are examined first; once those are
61
- # complete, questions w/ requirements are examined.
55
+ # prerequisites are examined first; once those are
56
+ # complete, questions w/ prerequisites are examined.
62
57
  # @return [void]
63
58
  def ask
64
- @prompts[:prompts].reject { |p| p[:requirements] }.each do |p|
59
+ @prefs.reject { |p| p.prereqs }.each do |p|
65
60
  _deliver_prompt(p)
66
61
  end
67
-
68
- @prompts[:prompts].find_all { |p| p[:requirements] }.each do |p|
69
- _deliver_prompt(p) if _requirements_fulfilled?(p)
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[:default]
75
+ default = p.default
81
76
 
82
77
  unless @configurator.nil?
83
- unless @configurator.data[p[:section].to_sym].nil?
84
- config_val = @configurator.data[p[:section].to_sym][p[:key].to_sym]
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
- if p[:options].nil?
90
- pref = prompt(p[:prompt], default)
91
- else
92
- valid_option_chosen = false
93
- until valid_option_chosen
94
- pref = prompt(p[:prompt], default)
95
- if p[:options].include?(pref)
96
- valid_option_chosen = true
97
- else
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
- p[:answer] = pref
103
- @answers << p
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
- # requirements have already been fulfilled.
106
+ # prerequisites have already been fulfilled.
108
107
  # @param [Hash] p The prompt
109
108
  # @return [void]
110
- def _requirements_fulfilled?(p)
109
+ def _prereqs_fulfilled?(p)
111
110
  ret = true
112
- p[:requirements].each do |req|
113
- a = @answers.detect do |answer|
114
- answer[:key] == req[:key] &&
115
- answer[:answer] == req[:value]
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
@@ -1,4 +1,4 @@
1
1
  module CLIUtils
2
2
  # The current version of the gem
3
- VERSION = '1.1.1'
3
+ VERSION = '1.2.0'
4
4
  end
data/lib/cliutils.rb CHANGED
@@ -8,6 +8,7 @@ require 'cliutils/configuration'
8
8
  require 'cliutils/logger-delegator'
9
9
  require 'cliutils/messenging'
10
10
  require 'cliutils/prefs'
11
+ require 'cliutils/prefs/pref'
11
12
  require 'cliutils/version'
12
13
 
13
14
  # The CLIUtils module, which wraps everything
Binary file
data/test/prefs_test.rb CHANGED
@@ -1,13 +1,13 @@
1
- require 'fileutils'
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=>"What is the hostname of your DD-WRT router?", :default=>"192.168.1.1", :key=>"hostname", :section=>"ssh_info"}, {:prompt=>"What is the SSH username of your DD-WRT router?", :default=>"root", :key=>"username", :section=>"ssh_info"}, {:prompt=>"What SSH port does your DD-WRT router use?", :default=>22, :key=>"port", :section=>"ssh_info"}, {:prompt=>"How do you use password or key authentication?", :default=>"password", :key=>"auth_method", :section=>"ssh_info", :options=>["password", "key"]}, {:prompt=>"Where is your key located?", :default=>"~/.ssh", :key=>"key_location", :section=>"ssh_info", :requirements=>[{:key=>"auth_method", :value=>"key"}]}, {:prompt=>"What is your password?", :key=>"password", :section=>"ssh_info", :requirements=>[{:key=>"auth_method", :value=>"password"}]}]
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
- assert_equal(YAML::load_file(@prefs_filepath).deep_symbolize_keys!, p.prompts)
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 test_hash_creation
27
+ def test_array_creation
26
28
  p = CLIUtils::Prefs.new(@prefs_arr)
27
- assert_equal({:prompts => @prefs_arr}.deep_symbolize_keys!, p.prompts)
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: What is the local hostname of your DD-WRT router?
3
- default: 192.168.1.1
4
- key: local_hostname
5
- section: ssh_info
6
- - prompt: What is the remote hostname of your DD-WRT router?
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.1.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-01 00:00:00.000000000 Z
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