http://www.perlmonks.org?node_id=159980
Category: Text Processing
Author/Contact Info stephen (steven@jubal.com)
Description:

I was writing up a list of comments on someone's code, and got tired of retyping the line number and filename over and over again. Also, I liked to skip around a bit in the files, but wanted to keep my annotations sorted.

So for starters, I wrote a little XEmacs LISP to automatically add my annotations to a buffer called 'Annotations'. It would ask me for the comment in the minibuffer, then write the whole thing, so that I could keep working without even having to switch screens. I bound it to a key so I could do it repeatedly. Pretty basic stuff.

annotate.el

(defun add-note () "Adds an annotation to the 'annotations' buffer" (interactive) (save-excursion (let ( (annotate-comment (read-from-minibuffer "Comment: ")) (annotate-buffer (buffer-name)) (annotate-line (number-to-string (line-number))) ) (set-buffer (get-buffer-create "annotations")) (goto-char (point-max)) (insert-string (concat annotate-buffer ":" annotate-line " " ann +otate-comment "\n" ) ) ) ) ) (global-set-key "\C-ca" `add-note)

This would generate a bunch of annotations like this:

comment_tmpl.tt2:1 This would be more readable if I turned on Template +'s space-stripping options. comment_reader.pl:31 More informative error message would probably be +good. comment_reader.pl:71 Need a better explanation of data structure. annotate.el:1 Should properly be in a mode... annotate.el:11 Should be configurable variable annotate.el:13 Formatting should be configurable in variable annotate.el:11 Should automatically make "annotations" visible if it i +sn't already annotate.el:21 Control-c keys are supposed to be for mode-specifics...

Next, I wanted to format my annotations so I could post them here in some kind of HTML format. So I wrote a little text processor to take my annotations, parse them, and format the result in HTML. This was not difficult, since most of the heavy lifting was done by the Template module.

Here's a standard template file... pretty ugly, really, but you can define your own without changing the code...

[% FOREACH file = files %][% FOREACH line = file.lines %] <dt>[% file.name %]</dt> <dd><b>line [% line.number %]</b> <ul>[% FOREACH comment = line.comments %] <li>[% comment %]</li> [% END %]</ul> </dd> [% END %][% END %]

Alternatively, I could have had my XEmacs function output XML and used XSLT. Six of one, half a dozen of the other... Plus, one could write a template file to translate annotations into an XML format.

The Output

annotate.el
line 1
  • Should properly be in a mode...
annotate.el
line 11
  • Should be configurable variable
  • Should automatically make "annotations" visible if it isn't already
annotate.el
line 13
  • Formatting should be configurable in variable
annotate.el
line 21
  • Control-c keys are supposed to be for mode-specifics...
comment_reader.pl
line 31
  • More informative error message would probably be good.
comment_reader.pl
line 71
  • Need a better explanation of data structure.
comment_tmpl.tt2
line 1
  • This would be more readable if I turned on Template's space-stripping options.
use strict;

####
#### comment_reader.pl
####
#### Reads a comment file from command-line arg or standard input
#### and outputs the result as a fragment of HTML suitable for
#### Perlmonks.
####

use IO::File;
use Getopt::Long;

use Template;

###################### Constants ######################

use constant DEFAULT_TEMPLATE => 'comment_tmpl.tt2';

###################### Main Program ###################

MAIN: {
  ## Get the options
  my $template_file = DEFAULT_TEMPLATE;
  my $outfile = undef;
  GetOptions('outfile=s' => \$outfile, 'template=s' => \$template_file
+);

  my $entries = read_entries();
  my $template = Template->new();
  $template->process($template_file, { files => $entries }, $outfile)
    or die $Template::ERROR;
}

###################### Subroutines #####################

##
## read_entries()
##
## Reads whatever entries are present on the command line,
## then returns them parsed and sorted as a reference
## to an array of hashes, like so:
##   [         # List of files
##     { 'name' => 
##
sub read_entries
{
  my %comments = ();
  while (<>) {
    chomp;
    
    # Bare-bones attempt at avoiding missing closing tags
    while (m{<(?!\/)([^>]+)>}g) {
      my $tag = $1;
      m{</$tag>}i or warn "Missing closing tag for $tag at $.\n";
    }

    my ($file_list, $comment) = split(/\s+/, $_, 2);
    foreach my $lineref (split(/;/, $file_list) ) {
      my ($file, $lines) = split(/:/, $lineref, 2);
      foreach my $line (split(/,/, $lines)) {
    push( @{ $comments{$file}{$line} }, $comment );
      }
    }
  }

  return sort_files(\%comments);
}

##
## Given a hash reference, turns it into a list of hashrefs.
##
sub sort_files {
  my ($comments) = @_;

  my @files = ();
  foreach my $file (sort keys %$comments) {
    my @lines = ();
    foreach my $line (sort {$a <=> $b} keys %{$comments->{$file}}) {
      push(@lines, { 'number' => $line, 'comments' => $comments->{$fil
+e}{$line} });
    }
    push (@files, { name => $file, lines => \@lines });
  }

  return \@files;
}
Replies are listed 'Best First'.
•Re: Annotating Files
by merlyn (Sage) on Apr 18, 2002 at 01:04 UTC
    You can keep the template right inside the program, and do the medium lifting with Template instead of Perl, like so:
    my %d; while (<DATA>) { my ($file, $line, $comment) = /^([^:]+):(\d+)\s+(.*)/ or warn("unknown line, skipping: $_"), next; push @{$d{$file}{$line}}, $comment; } use Template; Template->new->process(\<<'EOT', { d => \%d, env => \%ENV }) [%# USE Dumper; Dumper.dump(d); -%] [%- FOREACH filek = d.keys.sort; filev = d.$filek; FOREACH linek = filev.keys.nsort; linev = filev.$linek; "<dt>$filek</dt><dd><b> line $linek</b>\n"; FOREACH comment = linev; " <ul>\n" IF loop.first; " <li>"; comment | html; "</li>\n"; " </ul>\n" IF loop.last; END; "</dd>\n"; END; END; -%] EOT or die Template->error; __END__ comment_tmpl.tt2:1 This would be more readable if I turned on Template +'s space-stripping options. comment_reader.pl:31 More informative error message would probably be +good. comment_reader.pl:71 Need a better explanation of data structure. annotate.el:1 Should properly be in a mode... annotate.el:11 Should be configurable variable annotate.el:13 Formatting should be configurable in variable annotate.el:11 Should automatically make "annotations" visible if it i +sn't already annotate.el:21 Control-c keys are supposed to be for mode-specifics...
    which generates:
    annotate.el
    line 1
    • Should properly be in a mode...
    annotate.el
    line 11
    • Should be configurable variable
    • Should automatically make "annotations" visible if it isn't already
    annotate.el
    line 13
    • Formatting should be configurable in variable
    annotate.el
    line 21
    • Control-c keys are supposed to be for mode-specifics...
    comment_reader.pl
    line 31
    • More informative error message would probably be good.
    comment_reader.pl
    line 71
    • Need a better explanation of data structure.
    comment_tmpl.tt2
    line 1
    • This would be more readable if I turned on Template's space-stripping options.

    -- Randal L. Schwartz, Perl hacker

A reply falls below the community's threshold of quality. You may see it by logging in.