June 29, 2007 at 5:33 PM by Dr. Drang
As we left the story yesterday, I had decided that I needed three things to achieve my line-numbering goals:
- A script that I could use to number lines of code according to my picky aesthetic sensibilities.
<span>tags around all the line numbers.
- A bit of CSS to style the line numbers to my liking.
This post will cover the line numbering script. At first, I wrote a shell script that invoked the standard Unix line numbering utility,
nl with a set of options I liked. The script worked, but it assumed that the lines of text to be numbered were in a file—it couldn’t handle lines of text passed to it via
stdin. So it wasn’t truly a filter and I couldn’t select a bunch of lines in TextMate and have them numbered through the Filter Through Command… item in the Text menu. Furthermore, I realized that the script was going through the lines of text twice: once to see how many lines there were, and a second time to do the actual numbering. I know how to get a shell script to read from
stdin, I just don’t have the scripting prowess to save that input so it can be processed again.
I was quite confident that I could whip up a Perl script that would do what I want, but I’ve been trying to avoid Perl recently. I don’t buy the usual criticisms of Perl—unreadable, unmaintainable—but Perl 6 seems to be a tarpit that the language has wandered into, never to escape. I’ve been doing more Python recently, so here’s my Python line numbering program.
1: #!/usr/bin/env python 2: 3: from math import log10 4: import fileinput 5: 6: # Read the input lines into a list, prepending the line numbers 7: # and some separator text. 8: numberedLines =  9: for line in fileinput.input(): 10: numberedLines.append(str(fileinput.lineno()) + ': ' + line) 11: 12: # Determine the number of digits in the highest line number. 13: numberWidth = int(log10(fileinput.lineno())) + 1 14: 15: # Add spaces to the beginning of the shorter line numbers. 16: for i, line in enumerate(numberedLines): 17: num, rest = line.split(':', 1) 18: fnum = str(num).rjust(numberWidth) 19: numberedLines[i] = ':'.join([fnum, rest]) 20: 21: print ''.join(numberedLines)
I call it
anl, which you can think of as “another nl” or “automatic nl” (because it includes my preferred
nl options automatically) or just “anal.” It uses the
fileinput module, which allows it to read from a file or from standard input, like Perl’s
<> operator. The only moderately clever thing about
anl is in line 13, where it uses the
log10 function to determine the length of the highest line number. It’s only moderately clever, because it will give a result that is 1 higher than it should be if the highest line number is a power of 10: 10, 100, 1000, etc. This seemed like a rare enough occurrence that I could ignore the complicated coding necessary to fix it. If I ever include a block of code that’s 10 or 100 lines long, there will be an extra space in front of all the line numbers. (If I ever include a block of code that’s 1000 lines long, you have my permission to hunt me down and shoot me.) Update I thought I’d made a mistake, but I was wrong.