On Colorized Output 16 Dec 2011
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.
So grep
and 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...
Sensible Default?
When in doubt, follow suit. Tools like grep
and less
don't colorize output by default. But ack
, for instance, does colorize by default. So what should hilong
do?
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 --no-color
option.
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 colorized_output
to false
, it's simply assigned the value passed by optparse
.
optparse
encourages best practices by recognizing certain conventions. eg. --[no-]thing
for boolean options, --arg value
for required options.
So optparse
was able to figure that --no-color
is a boolean and that it should be negative (false) considering it's prefixed with a --no
. Rad.
Notice that the check for $stdout.tty?
has been removed. It no longer matters. Output is colorized unless disabled with the command-line option.
Now 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!
Much better.
Moar options
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!
Again 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 -m
or --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.
Tweet