Making Vim, Unix and Ruby sing harmony

ruby unix vim shell
Posted on: 2013-01-15

If you use Vim and Unix, you may know that you can execute shell commands from Vim, like :!mkdir foo.

You may not realize that you can use them to transform text. For instance, highlight some lines and type :. You should see run :'<,'>, indicating that the range for this command will be the highlighted lines. Add a couple of chained commands, like :'<,'>!uniq | sort, and hit enter. You'll see your highlighted text replaced with the output of those commands.

Now consider what's happening there: your highlighted text is being given to a shell command, uniq, which takes standard input. It writes to standard output, which you pipe to uniq, which transforms it again and writes to standard output. Vim takes the final output from the command and replaces your text.

Lesson: any command-line program which takes standard input and writes to standard output can be used to transform text in Vim.

You know what's great for writing scripts like that? Ruby. Here's one way I've used this trick.

Scramble those stanzas

Sometimes I need to take song lyrics (usually hymns) and add guitar chords. Song lyrics are, reasonably enough, generally written in the order that they're sung.

Joyful, joyful, we adore Thee, God of glory, Lord of love;
Hearts unfold like flowers before Thee, opening to the sun above.
Melt the clouds of sin and sadness; drive the dark of doubt away;
Giver of immortal gladness, fill us with the light of day!

All Thy works with joy surround Thee, earth and heaven reflect Thy rays,
Stars and angels sing around Thee, center of unbroken praise.
Field and forest, vale and mountain, flowery meadow, flashing sea,
Singing bird and flowing fountain call us to rejoice in Thee.

That's fine for singing along, but it's lousy for adding guitar chords. The first lines of these verses, "Joyful, joyful..." and "All Thy works...", need the same guitar chords. So do the second lines, and the third lines. I don't want to repeat the chords for every single verse.

However, if I rearrange the lines like this:

Joyful, joyful, we adore Thee, God of glory, Lord of love;
All Thy works with joy surround Thee, earth and heaven reflect Thy rays,

Hearts unfold like flowers before Thee, opening to the sun above.
Stars and angels sing around Thee, center of unbroken praise.
(etc)

... I only have to write the chords once; each paragraph of lines shares the same chords.

When I first realized this, I rearranged some lyrics manually. Adding chords was easy, but rearranging the words in the first place was tedious. Being lazy, I then wrote a Ruby script that 1) takes standard input, 2) rearranges lines like this, 3) puts the result to standard output. It looks like this:

#!/usr/bin/env ruby
# Rearrange song lyrics so it's easier to put guitar chords over them: put the
# first lines of each verse, which share the same chords, into the first
# paragraph. Second lines go into the second paragraph, etc.
#
# Meant to be used with Unix piping: `cat somefile.txt | ./this_script > output.txt`

lyrics           = STDIN.read
paragraphs       = lyrics.split("\n\n")
lines            = paragraphs.map { |paragraph| paragraph.split("\n") }

# This line, where the actual work gets done, is just a native Ruby method.
# I laughed when I learned that. Pretty much anything I've wanted to do with
# a collection, Ruby has already had a method for.
transposed_lines = lines.transpose 

new_paragraphs   = transposed_lines.map { |paragraph| paragraph.join("\n") }
new_lyrics       = new_paragraphs.join("\n\n")
puts new_lyrics

I named the file transpose_lyrics, without the .rb (no extension is needed because of the "shebang" on the first line) and put it on my path. Now I can open Vim, highlight some lines, and type :!transpose_lyrics. Bam! The lines are rearranged and ready for guitar chords.

Now that I know how to do this, I'm sure I'll find other text transformations that Ruby can make faster and easier.

It's so nice to have Vim, Unix and Ruby singing harmony.