Line numbering utility

Well, it’s obvious that the most anticipated tech story today is the followup to my post yesterday about line numbering in the code I post in this blog. (iWhat? Never heard of it.)

As we left the story yesterday, I had decided that I needed three things to achieve my line-numbering goals:

  1. A script that I could use to number lines of code according to my picky aesthetic sensibilities.
  2. A JavaScript function that goes through my HTML document and puts <span> tags around all the line numbers.
  3. 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.

You’ll note in line 10 that I chose to separate the line number from the rest of the line with a colon followed by two spaces. To my eye, this separation is both definite and unobtrusive when I’m looking at the Markdown source code in a text editor. You’ll also note that the colons don’t show up in the rendered page. That’s due to a bit of JavaScript that I’ll discuss in my next post.

Tags: