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 +4 -4
- data/HISTORY.md +4 -0
- data/README.md +121 -52
- data/lib/cliutils/constants.rb +1 -1
- data/lib/cliutils/ext/string_extensions.rb +24 -6
- data/lib/cliutils/prefs/pref.rb +92 -4
- data/lib/cliutils/prefs.rb +1 -11
- data/lib/cliutils.rb +1 -0
- data/test/configurator_test.rb +4 -4
- data/test/prefs_test.rb +3 -3
- data/test/test_files/prefstest.yaml +11 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcb17ad9640f90cacc87af17bac0d029d40ca4f0
|
4
|
+
data.tar.gz: 97a656dee8638fb13c0548d802b1c88a66b40e58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c54f72b7d9af949a9840a4f89325a6a767d646495611996bf8a60722bc20c72517d62904e9bb55c798c542fc45744811ae30fc30907e6983004a8f269e3c797d
|
7
|
+
data.tar.gz: 0af2677afcd529a66ce4d64a2483f2a79612ae3430c2efeee05850e562bcb68a3f6bfd66327c01b6d2101b64350abba55113e3d53d299e31b20ded8e06dda4b2
|
data/HISTORY.md
CHANGED
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.
|
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
|
-
```
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
193
|
+
## Logging
|
190
194
|
|
191
|
-
Often, it's desirable to log messages as they appear to your user. `
|
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
|
-
|
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
|
-
|
236
|
+
To use, mix it in!
|
231
237
|
|
232
238
|
```Ruby
|
233
239
|
include CLIUtils::Configuration
|
234
240
|
```
|
235
241
|
|
236
|
-
|
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 `
|
248
|
+
If there's data in there, it will be consumed into the `Configurator`'s `data` property.
|
243
249
|
|
244
|
-
|
250
|
+
## Adding/Removing Sections
|
245
251
|
|
246
|
-
Sections are top levels of the configuration file and are managed via the `
|
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
|
-
|
261
|
+
## Adding Data to Sections
|
256
262
|
|
257
|
-
There are two ways data can be managed in `
|
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
|
-
|
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
|
-
|
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
|
-
`
|
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
|
-
|
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
|
-
|
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
|
-
* `
|
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
|
-
-
|
354
|
+
- prompt_text: What is your name?
|
349
355
|
default: Bob Cobb
|
350
356
|
config_key: name
|
351
357
|
config_section: personal_info
|
352
|
-
-
|
358
|
+
- prompt_text: What is your age?
|
353
359
|
default: 45
|
354
360
|
config_key: age
|
355
361
|
config_section: personal_info
|
356
|
-
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
-
|
444
|
+
- prompt_text: Batman or Superman?
|
439
445
|
default: Batman
|
440
446
|
config_key: superhero
|
441
447
|
config_section: personal_info
|
442
|
-
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
|
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
|
-
-
|
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
|
-
|
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
|
-
|
676
|
+
## Using Configurator Values as Defaults
|
608
677
|
|
609
678
|
Note that you can also initialize a `Prefs` object with a Configurator:
|
610
679
|
|
data/lib/cliutils/constants.rb
CHANGED
@@ -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
|
data/lib/cliutils/prefs/pref.rb
CHANGED
@@ -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 :
|
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
|
-
@
|
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
|
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
|
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
|
data/lib/cliutils/prefs.rb
CHANGED
@@ -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
|
-
|
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
data/test/configurator_test.rb
CHANGED
@@ -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'
|
54
|
+
@config.app_data.merge!({ VERSION: '1.0.0' })
|
55
55
|
|
56
|
-
@config.
|
57
|
-
@config.
|
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
|
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
|
-
'
|
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
|
-
'
|
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
|
-
'
|
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
|
-
-
|
3
|
-
config_key:
|
4
|
-
config_section:
|
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.
|
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-
|
11
|
+
date: 2014-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|