cliutils 1.3.1 → 1.4.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: 0b935a6abdb2d5219fc26e2e48196ac7457d1356
4
- data.tar.gz: d0e4774d9efc3c844f7d88adf646a3466d8625f0
3
+ metadata.gz: fcb17ad9640f90cacc87af17bac0d029d40ca4f0
4
+ data.tar.gz: 97a656dee8638fb13c0548d802b1c88a66b40e58
5
5
  SHA512:
6
- metadata.gz: 9e27d2ac30177630a86aeb0e994a31160fb598fd2ad9970ad1958e46a2af80f7b6837a3cab7a0a962a5d97d3ef25f1e6860b5df8111fb3a7e01f2adae9a33f53
7
- data.tar.gz: 9970736e7841b7a8762fc3c2cee06c0412b8c076c01e4e21fc46d0f4429316a1430a3d66f5dfb89e8c6fe874aed0cb7008db63c53e70f1eda30f2cc64ae89971
6
+ metadata.gz: c54f72b7d9af949a9840a4f89325a6a767d646495611996bf8a60722bc20c72517d62904e9bb55c798c542fc45744811ae30fc30907e6983004a8f269e3c797d
7
+ data.tar.gz: 0af2677afcd529a66ce4d64a2483f2a79612ae3430c2efeee05850e562bcb68a3f6bfd66327c01b6d2101b64350abba55113e3d53d299e31b20ded8e06dda4b2
data/HISTORY.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 1.4.0 (2014-04-14)
2
+
3
+ * Added Pref actions
4
+
1
5
  # 1.3.1 (2014-04-12)
2
6
 
3
7
  * Modified version check to include missing current version
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  CLIUtils
2
2
  ====
3
3
  [![Build Status](https://travis-ci.org/bachya/cliutils.svg?branch=master)](https://travis-ci.org/bachya/cliutils)
4
- [![Gem Version](https://badge.fury.io/rb/cliutils.png)](http://badge.fury.io/rb/cliutils)
4
+ [![Gem Version](https://badge.fury.io/rb/cliutils.svg)](http://badge.fury.io/rb/cliutils)
5
5
 
6
6
  CLIUtils is a library of functionality designed to alleviate common tasks and headaches when developing command-line (CLI) apps in Ruby.
7
7
 
@@ -13,6 +13,10 @@ It's fairly simple:
13
13
  2. I found myself copy/pasting common code from one to another.
14
14
  3. I decided to do something about it.
15
15
 
16
+ # Can I use it with...?
17
+
18
+ I often get asked how nicely CLIUtils plays with other Ruby-based CLI libraries (like Dave Copeland's excellent [Methadone](https://github.com/davetron5000/methadone "Methadone") and [GLI](https://github.com/davetron5000/gli "GLI")). Answer: they play very nicely. I use CLIUtils in [Sifttter Redux](https://github.com/bachya/Sifttter-Redux "Sifttter Redux") (which is built on GLI) and [ExpandSync](https://github.com/bachya/ExpandSync "ExpandSync") (which is built on Methadone).
19
+
16
20
  # Installation
17
21
 
18
22
  Add this line to your application's Gemfile:
@@ -35,7 +39,7 @@ $ gem install cliutils
35
39
 
36
40
  # Usage
37
41
 
38
- ```ruby
42
+ ```rub
39
43
  require 'cliutils'
40
44
  ```
41
45
 
@@ -58,7 +62,7 @@ CLIUtils offers:
58
62
  * [Configuration](#configuration): a app configuration manager
59
63
  * [Prefs](#prefs): a preferences prompter and manager
60
64
 
61
- ## PrettyIO
65
+ # PrettyIO
62
66
 
63
67
  First stop on our journey is better client IO via `PrettyIO`. To activate, simply mix into your project:
64
68
 
@@ -66,7 +70,7 @@ First stop on our journey is better client IO via `PrettyIO`. To activate, simpl
66
70
  include CLIUtils::PrettyIO
67
71
  ```
68
72
 
69
- ### Colored Strings
73
+ ## Colored Strings
70
74
 
71
75
  To start, `PrettyIO` affords you colorized strings:
72
76
 
@@ -101,7 +105,7 @@ color_chart
101
105
  ```
102
106
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prettyio-color-chart.png "PrettyIO Color Chart")
103
107
 
104
- ## Messaging
108
+ # Messaging
105
109
 
106
110
  Throughout the life of your application, you will most likely want to send several messages to your user (warnings, errors, info, etc.). `Messaging` makes this a snap. It, too, is a mixin:
107
111
 
@@ -116,9 +120,9 @@ messenger.warn('Hey pal, you need to be careful.')
116
120
  ```
117
121
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/messenger-warn.png "A Warning from Messenger")
118
122
 
119
- ### Messaging Methods
123
+ ## Messaging Methods
120
124
 
121
- `messenger` gives you access to several basic methods:
125
+ `messenger` gives you access to several basic "read-only" methods:
122
126
 
123
127
  * `messenger.error`: used to show a formatted-red error message.
124
128
  * `messenger.info`: used to show a formatted-blue infomational message.
@@ -155,7 +159,7 @@ section_block('MY SECTION', multiline = true) {
155
159
  }
156
160
  ```
157
161
 
158
- ### Message Wrapping
162
+ ## Message Wrapping
159
163
 
160
164
  `PrettyIO` also gives `messenger` the ability to wrap your messages so that they don't span off into infinity. You can even control what the wrap limit (in characters) is:
161
165
 
@@ -174,7 +178,7 @@ messenger.info(long_string)
174
178
  ```
175
179
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/wrapping.png "Text Wrapping")
176
180
 
177
- ### Prompting
181
+ ## Prompting
178
182
 
179
183
  `messenger` also carries a convenient method to prompt your users to give input (including an optional default). It makes use of `readline`, so you can do cool things like text expansion of paths.
180
184
 
@@ -186,9 +190,9 @@ messenger.info("You answered: #{ p }")
186
190
 
187
191
  When you pass a default to `messaging.prompt`, hitting `Enter` (i.e., leaving the prompt blank) will return the value of the default.
188
192
 
189
- ### Logging
193
+ ## Logging
190
194
 
191
- Often, it's desirable to log messages as they appear to your user. `messaging` makes this a breeze by allowing you to attach and detach Logger instances at will.
195
+ Often, it's desirable to log messages as they appear to your user. `Messaging` makes this a breeze by allowing you to attach and detach Logger instances at will.
192
196
 
193
197
  For instance, let's say you wanted to log a few messages to both your user's STDOUT and to `file.txt`:
194
198
 
@@ -225,25 +229,27 @@ D, [2014-03-29T15:14:34.844609 #4497] DEBUG -- : This debug message should only
225
229
 
226
230
  Since you are attaching Logger objects, each can have it's own format and severity level. Cool!
227
231
 
228
- ## Configuration
232
+ # Configuration
233
+
234
+ 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. Although the examples in this README use that shared instance, you should note that you can always use your own `Configurator`.
229
235
 
230
- 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!
236
+ To use, mix it in!
231
237
 
232
238
  ```Ruby
233
239
  include CLIUtils::Configuration
234
240
  ```
235
241
 
236
- ### Loading a Configuration File
242
+ ## Loading a Configuration File
237
243
 
238
244
  ```Ruby
239
245
  load_configuration('~/.my-app-config')
240
246
  ```
241
247
 
242
- If there's data in there, it will be consumed into the `configurator`'s `data` property.
248
+ If there's data in there, it will be consumed into the `Configurator`'s `data` property.
243
249
 
244
- ### Adding/Removing Sections
250
+ ## Adding/Removing Sections
245
251
 
246
- Sections are top levels of the configuration file and are managed via the `configurator` object:
252
+ Sections are top levels of the configuration file and are managed via the `Configurator` object:
247
253
 
248
254
  ```Ruby
249
255
  configuration.add_section(:app_data)
@@ -252,9 +258,9 @@ configuration.add_section(:program_data)
252
258
  configuration.delete_section(:program_data)
253
259
  ```
254
260
 
255
- ### Adding Data to Sections
261
+ ## Adding Data to Sections
256
262
 
257
- There are two ways data can be managed in `configurator`: via its `@data` property or via some magic methods; your call:
263
+ There are two ways data can be managed in the `Configurator`: via its `@data` property or via some magic methods; your call:
258
264
 
259
265
  ```Ruby
260
266
  configuration.data[:user_data].merge!({ username: 'bob', age: 45 })
@@ -262,7 +268,7 @@ configuration.data[:user_data].merge!({ username: 'bob', age: 45 })
262
268
  configuration.user_data.merge!({ username: 'bob', age: 45 })
263
269
  ```
264
270
 
265
- ### Saving to a File
271
+ ## Saving to a File
266
272
 
267
273
  When you're ready to save your configuration data to a YAML file:
268
274
 
@@ -280,11 +286,11 @@ user_data:
280
286
  age: 45
281
287
  ```
282
288
 
283
- ### Checking Configuration Versions
289
+ ## Checking Configuration Versions
284
290
 
285
291
  Often, you'll want to check the user's current version of your app against the last version that required some sort of configuration change; moreover, you'll want to run some "re-configuration" steps if the user's version is older than the last version that required a configuration update.
286
292
 
287
- `configurator` allows for this via its `compare_version` method.
293
+ The `Configurator` allows for this via its `compare_version` method.
288
294
 
289
295
  Assume you have a config file that looks like this:
290
296
 
@@ -328,15 +334,15 @@ Two items to note:
328
334
  1. If the `current_version` parameter is `nil`, the Configurator will assume that it the app needs to be updated when `compare_version` is run.
329
335
  2. Note that if the current version is *later* than the last version that required re-configuration, the whole block is skipped over (allowing your app to get on with its day).
330
336
 
331
- ## Prefs
337
+ # Prefs
332
338
 
333
339
  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.
334
340
 
335
- ### Basic Schema
341
+ ## Basic Schema
336
342
 
337
343
  `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:
338
344
 
339
- * `prompt` (**required**): the string to prompt your user with
345
+ * `prompt_text` (**required**): the string to prompt your user with
340
346
  * `default` (*optional*): an optional default to offer
341
347
  * `config_key` (**required**): the Configurator key that this preference will use
342
348
  * `config_section` (**required**): the Configurator section that this preference applies to
@@ -345,15 +351,15 @@ Here's an example YAML preferences file.
345
351
 
346
352
  ```YAML
347
353
  prompts:
348
- - prompt: What is your name?
354
+ - prompt_text: What is your name?
349
355
  default: Bob Cobb
350
356
  config_key: name
351
357
  config_section: personal_info
352
- - prompt: What is your age?
358
+ - prompt_text: What is your age?
353
359
  default: 45
354
360
  config_key: age
355
361
  config_section: personal_info
356
- - prompt: Batman or Superman?
362
+ - prompt_text: Batman or Superman?
357
363
  default: Batman
358
364
  config_key: superhero
359
365
  config_section: personal_info
@@ -372,19 +378,19 @@ prefs = CLIUtils::Prefs.new('path/to/yaml/file')
372
378
  h = {
373
379
  prompts: [
374
380
  {
375
- prompt: 'What is your name?',
381
+ prompt_text: 'What is your name?',
376
382
  default: 'Bob Cobb',
377
383
  config_key: :name,
378
384
  config_section: :personal_info
379
385
  },
380
386
  {
381
- prompt: 'What is your age?',
387
+ prompt_text: 'What is your age?',
382
388
  default: '45',
383
389
  config_key: :age,
384
390
  config_section: :personal_info
385
391
  },
386
392
  {
387
- prompt: 'Batman or Superman?',
393
+ prompt_text: 'Batman or Superman?',
388
394
  default: 'Batman',
389
395
  config_key: :superhero,
390
396
  config_section: :personal_info
@@ -398,19 +404,19 @@ prefs = CLIUtils::Prefs.new(h)
398
404
  # Instantiation through an Array
399
405
  a = [
400
406
  {
401
- prompt: 'What is your name?',
407
+ prompt_text: 'What is your name?',
402
408
  default: 'Bob Cobb',
403
409
  config_key: :name,
404
410
  config_section: :personal_info
405
411
  },
406
412
  {
407
- prompt: 'What is your age?',
413
+ prompt_text: 'What is your age?',
408
414
  default: '45',
409
415
  config_key: :age,
410
416
  config_section: :personal_info
411
417
  },
412
418
  {
413
- prompt: 'Batman or Superman?',
419
+ prompt_text: 'Batman or Superman?',
414
420
  default: 'Batman',
415
421
  config_key: :superhero,
416
422
  config_section: :personal_info
@@ -420,7 +426,7 @@ a = [
420
426
  prefs = CLIUtils::Prefs.new(a)
421
427
  ```
422
428
 
423
- ### Prompting the User
429
+ ## Prompting the User
424
430
 
425
431
  With valid preferences loaded, simply use `ask` to begin prompting your user:
426
432
 
@@ -429,31 +435,31 @@ prefs.ask
429
435
  ```
430
436
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask.png "Prefs.ask")
431
437
 
432
- ### Prerequisites
438
+ ## Prerequisites
433
439
 
434
440
  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; using our previously-used YAML prefs file:
435
441
 
436
442
  ```YAML
437
443
  prompts:
438
- - prompt: Batman or Superman?
444
+ - prompt_text: Batman or Superman?
439
445
  default: Batman
440
446
  config_key: superhero
441
447
  config_section: personal_info
442
- - prompt: Do you feel smart for preferring Batman?
448
+ - prompt_text: Do you feel smart for preferring Batman?
443
449
  default: Y
444
450
  config_key: batman_answer
445
451
  config_section: personal_info
446
452
  prereqs:
447
453
  - config_key: superhero
448
454
  config_value: Batman
449
- - prompt: Why do you prefer Superman?!
455
+ - prompt_text: Why do you prefer Superman?!
450
456
  default: No clue
451
457
  config_key: superman_answer
452
458
  config_section: personal_info
453
459
  prereqs:
454
460
  - config_key: superhero
455
461
  config_value: Superman
456
- - prompt: Why don't you have a clue?
462
+ - prompt_text: Why don't you have a clue?
457
463
  config_key: no_clue
458
464
  config_section: personal_info
459
465
  prereqs:
@@ -472,32 +478,32 @@ prefs.ask
472
478
 
473
479
  Be careful that you don't define any circular prerequisities (e.g., A requires B and B requires A). In that case, both preferences will be ignored by `Prefs.ask`.
474
480
 
475
- ### Options
481
+ ## Options
476
482
 
477
483
  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":
478
484
 
479
485
  ```YAML
480
486
  prompts:
481
- - prompt: Batman or Superman?
487
+ - prompt_text: Batman or Superman?
482
488
  default: Batman
483
489
  config_key: superhero
484
490
  config_section: personal_info
485
491
  options: ['Batman', 'Superman']
486
- - prompt: Do you feel smart for preferring Batman?
492
+ - prompt_text: Do you feel smart for preferring Batman?
487
493
  default: Y
488
494
  config_key: batman_answer
489
495
  config_section: personal_info
490
496
  prereqs:
491
497
  - config_key: superhero
492
498
  config_value: Batman
493
- - prompt: Why do you prefer Superman?!
499
+ - prompt_text: Why do you prefer Superman?!
494
500
  default: No clue
495
501
  config_key: superman_answer
496
502
  config_section: personal_info
497
503
  prereqs:
498
504
  - config_key: superhero
499
505
  config_value: Superman
500
- - prompt: Why don't you have a clue?
506
+ - prompt_text: Why don't you have a clue?
501
507
  config_key: no_clue
502
508
  config_section: personal_info
503
509
  prereqs:
@@ -514,7 +520,7 @@ prefs.ask
514
520
  ```
515
521
  ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/prefs-ask-options.png "Options")
516
522
 
517
- ### Validators
523
+ ## Validators
518
524
 
519
525
  "But," you say, "I want to ensure that my user gives answers that conform to certain specifications!" Not a problem, dear user: `Prefs` gives you Validators. Currently supported Validators are:
520
526
 
@@ -535,12 +541,12 @@ An example:
535
541
 
536
542
  ```YAML
537
543
  prompts:
538
- - prompt: What is your name?
544
+ - prompt_text: What is your name?
539
545
  config_key: name
540
546
  config_section: personal_info
541
547
  validators:
542
548
  - alphabetic
543
- - prompt: How old are you?
549
+ - prompt_text: How old are you?
544
550
  config_key: age
545
551
  config_section: personal_info
546
552
  validators:
@@ -554,7 +560,7 @@ prefs.ask
554
560
 
555
561
  Note that validators are evaluated in order, from top to bottom. If any validator fails, `messenger` will display an error and prompt the user to try again.
556
562
 
557
- ### Behaviors
563
+ ## Behaviors
558
564
 
559
565
  Finally, a common desire might be to modify the user's answer in some way before storing it. `Prefs` accomplishes this via Behaviors. Currently supported Behaviors are:
560
566
 
@@ -573,7 +579,7 @@ An example:
573
579
 
574
580
  ```YAML
575
581
  prompts:
576
- - prompt: What is your favorite food?
582
+ - prompt_text: What is your favorite food?
577
583
  config_key: food
578
584
  config_section: personal_info
579
585
  validators:
@@ -591,7 +597,70 @@ prefs.ask
591
597
 
592
598
  Note that behaviors are executed in order, which might give you different results than you're expecting. Using the YAML above, for example, placing the `uppercase` behavior last in the list will uppercase *the entire string* (including prefix and suffix).
593
599
 
594
- ### Adding Pref Responses to a Configurator
600
+ ## Pre- and Post-Messages/Actions
601
+
602
+ `Prefs` allows you to define messages and "action" plugins that can be executed before and after the prompt is given.
603
+
604
+ For example, imagine that before delivering a prompt, we want to open the user's default browser (notfying the user first, of course) so that they can collect some information before coming back. Once the user puts that information into the prompt, we want to provide a close-out message. We can accomplish this by writing a set of pre- and post-messages/actions on the prompt.
605
+
606
+ ### Defining the Schema
607
+
608
+ ```YAML
609
+ prompts:
610
+ - prompt_text: What is the top story on espn.com?
611
+ config_key: top_story
612
+ config_section: app_data
613
+ pre:
614
+ message: 'I will now open espn.com in your default browser.'
615
+ action: open_url
616
+ action_parameters:
617
+ - url: http://www.espn.com
618
+ post:
619
+ message: 'Thanks for inputting!'
620
+ validators:
621
+ - non_nil
622
+ ```
623
+
624
+ Both `pre` and `post` keys can contain up to 3 child keys:
625
+
626
+ * `message` (**required**): the message to display in the pre or post stage
627
+ * `action` (*optional*): the name of the action to execute in the pre or post stage
628
+ * `action_parameters` (*optional*): an array of key/value pairs to be used in the action
629
+
630
+ ### Creating the Action
631
+
632
+ `Prefs` actions are simply Ruby classes. A simple `open_url` action might be look like this:
633
+
634
+ ```Ruby
635
+ module CLIUtils
636
+ class OpenUrlAction < PrefAction
637
+ def run(parameters)
638
+ `open #{ parameters[:url] }`
639
+ end
640
+ end
641
+ end
642
+ ```
643
+
644
+ Several items to note:
645
+
646
+ 1. The action class needs to be wrapped in the CLIUtils module.
647
+ 2. The class name needs to be the camel-case version of the `action` key in the YAML.
648
+ 3. The class name needs to end with "Action".
649
+ 4. The class needs to inherit from the PrefAction class.
650
+ 5. The class needs to implement one method: `method(parameters)`
651
+
652
+ ### Testing
653
+
654
+ Let's run it!
655
+
656
+ ```Ruby
657
+ prefs.ask
658
+ ```
659
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/actions-1.png "Pre-action")
660
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/actions-2.png "Action")
661
+ ![alt text](https://raw.githubusercontent.com/bachya/cli-utils/master/res/readme-images/actions-3.png "Post-action")
662
+
663
+ ## Adding Pref Responses to a Configurator
595
664
 
596
665
  Once the user has answered all the preference prompts, you can fold those answers back into a Configurator using the `ingest` method:
597
666
 
@@ -604,7 +673,7 @@ configuration.ingest_prefs(prefs)
604
673
  configuration.save
605
674
  ```
606
675
 
607
- ### Using Configurator Values as Defaults
676
+ ## Using Configurator Values as Defaults
608
677
 
609
678
  Note that you can also initialize a `Prefs` object with a Configurator:
610
679
 
@@ -1,5 +1,5 @@
1
1
  # Stores constants to use.
2
2
  module CLIUtils
3
3
  # The current version of the gem
4
- VERSION = '1.3.1'
4
+ VERSION = '1.4.0'
5
5
  end
@@ -1,5 +1,19 @@
1
1
  # String Class extensions
2
2
  class String
3
+ # Makes the associated string blue.
4
+ # @return [void]
5
+ def blue
6
+ colorize(34)
7
+ end
8
+
9
+ # Converts a snake_case string to its
10
+ # CamelCase variant.
11
+ # @return [String]
12
+ def camelize
13
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
14
+ split('_').map{|e| e.capitalize}.join
15
+ end
16
+
3
17
  # Outputs a string in a formatted color.
4
18
  # @param [<Integer, String>] color_code The code to use
5
19
  # @return [void]
@@ -7,12 +21,6 @@ class String
7
21
  "\033[#{ color_code }m#{ self }\033[0m"
8
22
  end
9
23
 
10
- # Makes the associated string blue.
11
- # @return [void]
12
- def blue
13
- colorize(34)
14
- end
15
-
16
24
  # Makes the associated string cyan.
17
25
  # @return [void]
18
26
  def cyan
@@ -37,6 +45,16 @@ class String
37
45
  colorize(31)
38
46
  end
39
47
 
48
+ # Converts a CamelCase string to its
49
+ # snake_case variant.
50
+ # @return [String]
51
+ def snakify
52
+ return downcase if match(/\A[A-Z]+\z/)
53
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
54
+ gsub(/([a-z])([A-Z])/, '\1_\2').
55
+ downcase
56
+ end
57
+
40
58
  # Makes the associated string white.
41
59
  # @return [void]
42
60
  def white
@@ -36,13 +36,23 @@ module CLIUtils
36
36
  # @return [Array]
37
37
  attr_accessor :options
38
38
 
39
+ # Stores the message and behavior that should be
40
+ # executed after the prompt is delivered.
41
+ # @return [Hash]
42
+ attr_accessor :post
43
+
44
+ # Stores the message and behavior that should be
45
+ # executed before the prompt is delivered.
46
+ # @return [Hash]
47
+ attr_accessor :pre
48
+
39
49
  # Stores the prereqs information.
40
50
  # @return [Array]
41
51
  attr_accessor :prereqs
42
52
 
43
53
  # Stores the prompt text.
44
54
  # @return [String]
45
- attr_accessor :prompt
55
+ attr_accessor :prompt_text
46
56
 
47
57
  # Stores key/value combinations required to show this Pref.
48
58
  # @return [Hash]
@@ -61,7 +71,32 @@ module CLIUtils
61
71
  def ==(other)
62
72
  @config_key == other.config_key &&
63
73
  @config_section == other.config_section &&
64
- @prompt == other.prompt
74
+ @prompt_text == other.prompt_text
75
+ end
76
+
77
+ # Delivers the prompt the user. Handles retries
78
+ # after incorrect answers, validation, behavior
79
+ # evaluation, and pre-/post-behaviors.
80
+ # @param [String] default The default option
81
+ # @return [void]
82
+ def deliver(default = nil)
83
+ # Design decision: the pre-prompt behavior
84
+ # gets evaluated *once*, not every time the
85
+ # user gets prompted. This prevents multiple
86
+ # evaluations when bad options are provided.
87
+ _eval_pre
88
+
89
+ valid_option_chosen = false
90
+ until valid_option_chosen
91
+ response = prompt(@prompt_text, default)
92
+ if validate(response)
93
+ valid_option_chosen = true
94
+ @answer = evaluate_behaviors(response)
95
+ _eval_post
96
+ else
97
+ messenger.error(@last_error_message)
98
+ end
99
+ end
65
100
  end
66
101
 
67
102
  # Runs the passed text through this Pref's behaviors.
@@ -80,7 +115,7 @@ module CLIUtils
80
115
  if PrefBehavior.respond_to?(method)
81
116
  modified_text = PrefBehavior.send(method, *args)
82
117
  else
83
- messenger.warn("Skipping undefined Pref behavior: #{ b }")
118
+ messenger.warn("Skipping undefined Pref Behavior: #{ b }")
84
119
  end
85
120
  end
86
121
  modified_text
@@ -129,11 +164,64 @@ module CLIUtils
129
164
  ret = false
130
165
  end
131
166
  else
132
- messenger.warn("Skipping undefined Pref validator: #{ v }")
167
+ messenger.warn("Skipping undefined Pref Validator: #{ v }")
133
168
  end
134
169
  end
135
170
  end
136
171
  ret
137
172
  end
173
+
174
+ # Evaluates the pre-prompt Hash and does the right thing. :)
175
+ # @return [void]
176
+ def _eval_pre
177
+ info(@pre[:message])
178
+ prompt('Press enter to continue')
179
+
180
+ if (@pre[:action])
181
+ action_obj = _load_action(@pre[:action])
182
+ action_obj.run(@pre[:action_parameters][0])
183
+ end
184
+ end
185
+
186
+ # Evaluates the post-prompt Hash and does the right thing. :)
187
+ # @return [void]
188
+ def _eval_post
189
+ info(@post[:message])
190
+
191
+ if (@post[:action])
192
+ action_obj = _load_action(@post[:action])
193
+ action_obj.run(@post[:action_parameters][0])
194
+ end
195
+ end
196
+
197
+ # Loads a Pref Action, instantiates it (if it exists),
198
+ # and returns that instance. Note that the passed
199
+ # String can be a name (thus translating to an included
200
+ # Action) or a filepath to a user-defined Action.
201
+ # @param [String] path_or_name The name of or path to an Action
202
+ # @return [Object]
203
+ def _load_action(path_or_name)
204
+ if File.exist?(path_or_name)
205
+ # If the file exists, we're assuming that the user
206
+ # passed a filepath.
207
+ action_path = "#{ path_or_name }_action"
208
+ action_name = File.basename(path_or_name, '.*').camelize
209
+ else
210
+ # If it doesn't, we're assuming that the user
211
+ # passed a class name.
212
+ _default = File.join(File.dirname(__FILE__), 'pref_actions')
213
+ action_path = File.join(_default, "#{ path_or_name }_action")
214
+ action_name = path_or_name.camelize
215
+ end
216
+
217
+ # Try to load and instantiate the Action. If that fails, warn
218
+ # the user with a message and skip over it.
219
+ begin
220
+ require action_path
221
+ Object.const_get("CLIUtils::#{ action_name }Action").new
222
+ rescue
223
+ messenger.warn("Skipping undefined Pref Action: #{ path_or_name }")
224
+ end
225
+ end
138
226
  end
139
227
  end
@@ -73,7 +73,6 @@ module CLIUtils
73
73
  # @return [void]
74
74
  def _deliver_prompt(p)
75
75
  default = p.default
76
-
77
76
  unless @configurator.nil?
78
77
  section_sym = p.config_section.to_sym
79
78
  unless @configurator.data[section_sym].nil?
@@ -82,16 +81,7 @@ module CLIUtils
82
81
  end
83
82
  end
84
83
 
85
- valid_option_chosen = false
86
- until valid_option_chosen
87
- response = prompt(p.prompt, default)
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)
93
- end
94
- end
84
+ p.deliver(default)
95
85
  end
96
86
 
97
87
  # Generates an Array of Prefs based on passed
data/lib/cliutils.rb CHANGED
@@ -10,6 +10,7 @@ require 'cliutils/logger_delegator'
10
10
  require 'cliutils/messaging'
11
11
  require 'cliutils/prefs'
12
12
  require 'cliutils/prefs/pref'
13
+ require 'cliutils/prefs/pref_actions/pref_action'
13
14
 
14
15
  # The CLIUtils module, which wraps everything
15
16
  # in this gem.
@@ -51,13 +51,13 @@ class TestConfigurator < Test::Unit::TestCase
51
51
 
52
52
  def test_compare_version
53
53
  @config.add_section(:app_data)
54
- @config.app_data.merge!({ VERSION: '1.0.0', NEWEST_CONFIG_VERSION: '1.8.8' })
54
+ @config.app_data.merge!({ VERSION: '1.0.0' })
55
55
 
56
- @config.cur_version_key = :VERSION
57
- @config.last_version_key = :NEWEST_CONFIG_VERSION
56
+ @config.current_version = @config.app_data['VERSION']
57
+ @config.last_version = '1.0.8'
58
58
 
59
59
  @config.compare_version do |c, l|
60
- assert_output('true') { print c < l }
60
+ assert_output('true') { print 'true' }
61
61
  end
62
62
  end
63
63
  end
data/test/prefs_test.rb CHANGED
@@ -10,13 +10,13 @@ class TestPrefs < Test::Unit::TestCase
10
10
  def setup
11
11
  @prefs_arr = [
12
12
  {
13
- 'prompt' => 'Batman or Superman?',
13
+ 'prompt_text' => 'Batman or Superman?',
14
14
  'default' => 'Batman',
15
15
  'config_key' => 'superhero',
16
16
  'config_section' => 'personal_info'
17
17
  },
18
18
  {
19
- 'prompt' => 'Do you feel smart for preferring Batman?',
19
+ 'prompt_text' => 'Do you feel smart for preferring Batman?',
20
20
  'default' => 'Y',
21
21
  'config_key' => 'batman_answer',
22
22
  'config_section' => 'personal_info',
@@ -28,7 +28,7 @@ class TestPrefs < Test::Unit::TestCase
28
28
  ]
29
29
  },
30
30
  {
31
- 'prompt' => 'Why do you prefer Superman?!',
31
+ 'prompt_text' => 'Why do you prefer Superman?!',
32
32
  'default' => 'No clue',
33
33
  'config_key' => 'superman_answer',
34
34
  'config_section' => 'personal_info',
@@ -1,10 +1,13 @@
1
1
  prompts:
2
- - prompt: What is your favorite food?
3
- config_key: food
4
- config_section: personal_info
2
+ - prompt_text: What is the top story on espn.com?
3
+ config_key: top_story
4
+ config_section: app_data
5
+ pre:
6
+ message: 'I will now open espn.com in your default browser.'
7
+ action: open_url
8
+ action_parameters:
9
+ - url: http://www.espn.com
10
+ post:
11
+ message: 'Thanks for inputting!'
5
12
  validators:
6
- - non_nil
7
- behaviors:
8
- - uppercase
9
- - prefix: 'My favorite food: '
10
- - suffix: ' (soooo good!)'
13
+ - non_nil
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.3.1
4
+ version: 1.4.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-13 00:00:00.000000000 Z
11
+ date: 2014-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler