CLIUtils

CLIUtils is a library of functionality designed to alleviate common tasks and headaches when developing command-line (CLI) apps in Ruby.

Why?

It's fairly simple:

  1. I love developing Ruby-based CLI apps.

  2. I found myself copy/pasting common code from one to another.

  3. I decided to do something about it.

Installation

Add this line to your application's Gemfile:

$ gem 'cliutils'

And then execute:

$ bundle

Or install it yourself:

$ gem install cliutils

Usage

require 'cliutils'

If you want to mix in everything that CLIUtils has to offer:

include CLIUtils

Alternatively, as described below, mix in only the libraries that you want.

Note that although this README.md is extensive, it may not cover all methods. Check out the tests to see more examples.

Libraries

CLIUtils offers:

PrettyIO

First stop on our journey is better client IO. To activate, simply mix into your project:

include CLIUtils::PrettyIO

PrettyIO affords you colorized strings:

puts 'A sample string'.red

PrettyIO gives you utility methods for the common ANSI color codes:

String.blue
String.cyan
String.green
String.purple
String.red
String.white
String.yellow

You also get the colorize method, which allows you to define more complex color combinations. For example, to get some nice purple text on a gnarly green background:

puts 'A sample string'.colorize('35;42')

Naturally, memorizing the ANSI color scheme is a pain, so PrettyIO gives you a convenient method to look up these color combinations:

color_chart

Messenging

Throughout the life of your application, you will most likely want to send several messages to your user (warnings, errors, info, etc.). Messenging makes this a snap. It, too, is a mixin:

include CLIUtils::Messenging

Once mixed in, you get access to messenger, a type of Logger that uses PrettyIO to send nicely-formatted messages to your user. For example, if you'd like to warn your user:

messenger.warn('Hey pal, you need to be careful.')

Messenging Methods

messenger gives you access to several basic methods:

Let's see an example that uses them all:

messenger.section('STARTING ATTACK RUN...')
messenger.info('Beginning strafing run...')
messenger.warn('WARNING: Tie Fighters approaching!')
messenger.error('Porkins died :(')
messenger.success('But Luke still blew up the Death Star!')

messenger also includes two “block” methods that allow you to wrap program execution in messages that are “longer-term”.

messenger.info_block('Starting up...', 'Done!', multiline = false) { # do stuff here }

messenger outputs 'Starting up…', runs the code in # do stuff here, and once complete, outputs 'Done!' on the same line. Note that section_block is the same exact signature (except for the method name, of course!).

Message Wrapping

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:

CLIUtils::PrettyIO::wrap_at(50)
messenger.info('This is a really long message, okay? It should wrap at some point. Seriously. Wrapping is nice.')
puts ''
CLIUtils::PrettyIO::wrap_at(20)
messenger.info('This is a really long message, okay? It should wrap at some point. Seriously. Wrapping is nice.')
puts ''
CLIUtils::PrettyIO::wrap(false)
messenger.info('This is a really long message, okay? It should wrap at some point. Seriously. Wrapping is nice.')

Prompting

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.

p = messenger.prompt('Are you a fan of Battlestar Galactica?', default = 'Y')
messenger.info("You answered: #{ p }")

Logging

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.

For instance, let's say you wanted to log a few messages to both your user's STDOUT and to file.txt:

file_logger = Logger.new('file.txt')

messenger.info('This should only appear in STDOUT.')

messenger.attach(file_logger)

messenger.warn('This warning should appear in STDOUT and file.txt')
messenger.error('This error should appear in STDOUT and file.txt')
messenger.debug('This debug message should only appear in file.txt')

messenger.detach(file_logger)

messenger.section('This section message should appear only in STDOUT')

In STDOUT:

…and in file.txt:

W, [2014-03-29T15:14:34.844406 #4497]  WARN -- : This warning should appear in STDOUT and file.txt
E, [2014-03-29T15:14:34.844553 #4497] ERROR -- : This error should appear in STDOUT and file.txt
D, [2014-03-29T15:14:34.844609 #4497] DEBUG -- : This debug message should only appear in file.txt

Since you can attach Logger objects, each can have it's own format and severity level. Cool!

Configuration

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!

include CLIUtils::Configuration

Loading a Configuration File

load_configuration('~/.my-app-config')

If there's data in there, it will be consumed into configuration's data property.

Adding/Removing Sections

Sections are top levels of the configuration file and are managed via the configuration object:

configuration.add_section(:user_data)
configuration.add_section(:program_data)
configuration.delete_section(:program_data)

Adding Data to Sections

There are two ways data can be managed in configuration: via its @data property or via some magic methods; your call:

configuration.data[:user_data].merge!(username: 'bob')
# OR
configuration.user_data.merge!(username: 'bob')

Saving to a File

When you're ready to save your configuration data to a YAML file:

configuration.save

Note that all your keys are converted to strings before saving (and, likewise, are converted to symbols, when loading). Assuming we used the commands above, we could expect this to be the contents of ~/.my-app-config:

---
user_data:
  username: bob

Known Issues

Bugs and Feature Requests

To report bugs with or suggest features/changes for CLIUtils, please use the Issues Page.

Contributing

Contributions are welcome and encouraged. To contribute:

  1. Fork it ( github.com/bachya/cliutils/fork )

  2. Create your feature branch (git checkout -b my-new-feature)

  3. Commit your changes (git commit -am 'Add some feature')

  4. Push to the branch (git push origin my-new-feature)

  5. Create new Pull Request

License

(The MIT License)

Copyright © 2014 Aaron Bach bachya1208@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.