On Colorized Output
Published on December 16, 2011 by Jesse Storimer
Colorized output should be configurable.
This is a followup to my last post.
In that post I said:
When you are piping output to another program you should always send plain, unformatted text. Unix utilities expect to deal with plain text.
A redditor commented on that post to this effect: (paraphrasing is mine)
Modern pagers and other programs can handle colorized output just fine, so colorized output from your scripts should be configurable.
If you're redirecting to a file, or some other program that doesn't understand escaped color sequences, then colorized output can be turned off, otherwise it'll probably work just fine.
Good advice, and he's absolutely right.
I started from the same script used in the last post and removed the check for
$stdout.tty? so that colorized output is always on. Now to experiment.
$ hilong Gemfile | less $ hilong Gemfile | grep gem $ hilong Gemfile | more $ hilong Gemfile > out.put
Your results may vary depending on your system and your configuration, but for me the first two experiments worked just fine and the second two didn't.
less handled the color codes fine, but
more didn't. Redirecting to a file obviously just preserved the raw text, which won't be interpreted by text editors and the like when it's opened later.
So there needs to be some way to turn colorized output on...or off...
When in doubt, follow suit. Tools like
less don't colorize output by default. But
ack, for instance, does colorize by default. So what should
Given that the 'highlighting' is basically the point of this tool I think that colorized output should be enabled by default. For most modern tools colorized output will not cause a problem, disabling colorized output will be a special case.
But there needs to be an option for that.
Parsing command-line options
Ruby's standard library has a class just for parsing command-line options. It's called
optparse. Using the script from the last blog post, here's how to use
optparse to accept a
maximum_line_length = 80 colorized_output = true require 'optparse' OptionParser.new do |options| options.on("--no-color", "Disable colorized output") do |color| colorized_output = color end end.parse! # Keep reading lines of input as long as they're coming. while input = ARGF.gets input.each_line do |line| # Construct a string that begins with the length of this line # and ends with the content. The trailing newline is #chop'ped # off of the content so we can control where the newline occurs. # The string are joined with a tab character so that indentation # is preserved. output_line = [line.size, line.chop].join("\t") # If the line is long and our $stdout is not being piped then we'll # colorize this line. if colorized_output && line.size > maximum_line_length # Turn the output to red starting at the first character. output_line.insert(0, red)
Something of interest is that
colorized_output is assigned to
true at the beginning, and then is assigned the value of the parsed option if that was passed in. Nowhere is it specified that
--no-color should set
false, it's simply assigned the value passed by
optparse encourages best practices by recognizing certain conventions. eg.
--[no-]thing for boolean options,
--arg value for required options.
optparse was able to figure that
--no-color is a boolean and that it should be negative (false) considering it's prefixed with a
Notice that the check for
$stdout.tty? has been removed. It no longer matters. Output is colorized unless disabled with the command-line option.
hilong can be used with that option to disable colorized output.
$ hilong --no-color Gemfile $ cat Gemfile Gemfile.lock | hilong --no-color | more
Perfect. And thanks to
optparse there's also some nice documentation showing which options are supported.
$ hilong -h Usage: hilong [options] --no-color Disable colorized output
Not bad, but it's easy to make that look better.
require 'optparse' OptionParser.new do |options| # This banner is the first line of the help documentation. options.banner = "Usage: hilong [options] [files]\n" \ "Show character count for each line of input and highlight long lines." # Separator just adds a new line with the specified text. options.separator "" options.separator "Specific options:" options.on("--no-color", "Disable colorized output") do |color| colorized_output = color end # on_tail says that this option should appear at the bottom of # the options list. options.on_tail("-h", "--help", "You're looking at it!") do $stderr.puts options exit 1 end end.parse!
Now the help docs look like this:
$ hilong -h Usage: hilong [options] [files] Show character count for each line of input and highlight long lines. Specific options: --no-color Disable colorized output -h, --help You're looking at it!
I want to show one more case with
optparse. Currently the script assumes that long lines are 80 chars or more. It's generally accepted that 80 chars is a long line but some some devs push that number to 120, or something else entirely. The maximum line length should be configurable.
end options.on("-m", "--max NUM", Integer, "Maximum line length") do |max| maximum_line_length = max end end.parse!
optparse is making a few assumptions about the options, but it does a pretty good job of it. It's set to accept an
Integer along with the
--max option. The result is assigned to the previously existing local variable and is handled by the rest of the script.
Now it can be used like so:
$ cat Gemfile* | hilong --max 20 $ hilong --m 140 Gemfile $ hilong --max=180 Gemfile
Try to pass something besides an Integer with the
max option and
optparse rightfully complains.
Full source of the hilong script is at https://gist.github.com/1465437.
The ruby-core documentation for OptionParser contains a useful example if you want to know more.