I found a new way to save time with Vim the other day by creating a script to reformat dates. I’ll walk you through how to do this, and why it’s worth your while. Here is a sample of a specification for a project I was working on recently, which specifies that on a page, there should be links to dated entries. To try this out as I explain, it, you can copy this into a file and follow along with these Vim commands.
Given I have a diary entry for October 31, 2010 When I look at my diary page Then I should see a link "Oct 31, 2010"
Given my sister has a private diary entry for April 4, 2010
When I look at my sister's diary page
Then I should not see a link "Apr 4, 2010"
Given our mother has a public diary entry for May 18, 1998
When I look at our mother's diary page
Then I should see a link "May 18, 1998"
This date format was already established in our hundreds of automated tests, when, of course, we got the request to change the format of the dates from Jan 12, 1997 to 01/12/97. Making the change to the helper method that styles those links was easy. Updating all the existing tests was another matter entirely. Why? Because that link format appears three times in the specification. I don't want to have to hunt through the file, count on my fingers to figure out if the month of July is the 6th or 7th month, and then do it again and again. What am I, some kind of robot? Why should I do three things when I could only do one? Here's how you can do what I did, so that Vim, not you, is the one doing three things.
- First, make sure you can find the dates you want to change, without finding anything else. Use this search pattern:
/[A-Z][a-z][a-z] \+\d\{1,2\}, \d\{4\}
Here’s how this works: it looks for a capital letter followed by two lowercase letters for the month, a space, 1 or 2 digits for the day, a comma, and 4 digits for the year.
You’ll need to reuse that pattern in a minute, so store it to a register. I saved it to the named u register (u for "ugly") by running
:let @u = @/
2. How can you reformat just one date before doing it to the entire file at once? The date command does a nice job of that. Try it out on the command line and you’ll get
date +%D -d "Nov 1, 2010"
3. Use two Vim registers to call the date command and feed it the existing date string. Move the cursor to the first matching date by running
/<Ctrl-r>u
Then change the date inside the quotes by typing
ci"<Ctrl-r>=system('date +%D -d "<Ctrl-r>""')
This will delete the existing date and save it into the unnamed (or ") register, make a system call through the expression (or =) register, and read the contents of the unnamed register to pass into the system call.
- There is one small problem—there's an extra newline in the result. You can remove that by changing the above to
ci"<Ctrl-r>=system('date +%D -d "<Ctrl-r>""' | tr -d '\n')
- That's too much typing to do once, and you would still have to repeat it twice more to finish changing this file. Fortunately, you can also use a Vim register to store a sequence of commands, like this
qd/<Ctrl-r>uci"<Ctrl-r>=system('date +%D -d "<Ctrl-r>"" | tr -d "\n" ')q
This is how it works: "record into the d register: search for the pattern in the u register, change the result using the date command, stop recording."
6.Now type @d to replay the macro you just recorded. It will find the next matching date and reformat it.
- But that would still entail doing three things instead of one thing. Here's the last bit.
:%normal @d
will run the macro over every line in the file, making the change if the search matches that line, and silently doing nothing if the search doesn't match that line.
You can now do one thing, and Vim will do three (or maybe more).
For more Vim tips, check out my slides from our internal conference.