LGTD commands and scripts

This is the fifth (and last?) in my series of posts on my LGTD bundle for TextMate. The first four posts cover

  1. installation and elementary use
  2. printing next action lists onto index cards
  3. snippets; and
  4. generating a project list

This post will discuss the scripts that make the system work.

The most important feature of LGTD is how it coordinates and syncs two separate next action lists: the project-centric list (“projects.gtd”) and the context-centric list (“contexts.gtd”). (Examples of these lists are given in the first LGTD post.) The coordination is done through a command called “Sort and save,” which looks like this:

It’s Key Equivalent is Command-S, so it intercepts the usual action of saving the current document and does its own things instead. The first thing it does, fortunately, is save the current document, so it meets your expectation that Command-S saves. It then runs the given shell script. This script is a simple front end to a pair of more complicated Perl scripts which do the real work of coordinating the two files. If you’re editing projects.gtd, contexts.gtd will be rewritten to reflect the changes you made. Conversely, if you’re editing contexts.gtd, then projects.gtd will be rewritten. By tying this behavior to the habitual action of hitting Command-S, the two files will stay in sync.

The Perl program that rewrites contexts.gtd based on the contents of projects.gtd is called “projects2contexts.pl” and looks like this

#!/usr/bin/env perl

# Project and context files.
$project_file = "$ENV{'TM_LGTD_DIRECTORY'}/projects.gtd";
$context_file = ">$ENV{'TM_LGTD_DIRECTORY'}/contexts.gtd";

# Initialize the actions hash and the waiting string.
%actions = ();
$waiting = '';

# Read in one project at a time.
$/ = "\n# ";

# Open the project file for reading.
open PROJECTS, $project_file or die "Can't open projects file: $!";

# Loop through the projects, gathering all the next action lines
# and adding them to the contexts hash.
while ($stanza = <PROJECTS>) {
  @lines = split("\n", $stanza);
  ($project) = shift(@lines) =~ /^#? ?([^]]+) #$/m;
  for (@lines) {
    if (/^\*/) {
      ($act, $context) = /^(\*[^[]+) +\[([^]]+)]/;
      $actions{$context} .= "$act [$project]\n";
    }
    elsif (/^-/) {
      chomp;
      $waiting .= "$_ [$project]\n";
    }
    else {
      next;
    }
  }
}
close PROJECTS;

# Open the contexts file for printing.
open CONTEXTS, $context_file or die "Can't open context file: $!";
select CONTEXTS;

# Print the next actions according to context.
for (sort keys %actions) {
  $title = $_;
  substr($title, 0, 1) =~ tr/a-z/A-Z/;
  print "# $title #\n\n";
  print $actions{$_};
  print "\n";
}

# Print the things we're waiting for.
print "# Waiting for #\n\n";
print $waiting;

close CONTEXTS;

The Perl program that rewrites projects.gtd based on the contents of contexts.gtd is called “contexts2projects.pl” and looks like this

#!/usr/bin/env perl

# Project and context files.
$context_file = "$ENV{'TM_LGTD_DIRECTORY'}/contexts.gtd";
$project_file = ">$ENV{'TM_LGTD_DIRECTORY'}/projects.gtd";

# Initialize the actions and waiting hashes.
%actions = ();
%waiting = ();

# Read in one context at a time.
$/ = "\n# ";

# Open the contexts file for reading.
open CONTEXTS, $context_file or die "Can't open contexts file: $!";

# Loop through the contexts, gathering all the next action lines
# and adding them to the actions hash and all the hold lines and
# adding them to the waiting hash.
while ($stanza = <CONTEXTS>) {
  @lines = split("\n", $stanza);
  ($context) = shift(@lines) =~ /^#? ?([^]]+) #$/m;
  $context = lc($context);
  if ($context eq 'waiting for') {
    $context =~ s/ for$//;
    for (@lines) {
      if (/^-/) {
        ($hold, $project) = /^(-[^[]+) +\[([^]]+)]/;
        $waiting{$project} .= "$hold\n";
      }
    }
  }
  else {
    for (@lines) {
      if (/^\*/) {
        ($act, $project) = /^(\*[^[]+) +\[([^]]+)]/;
        $actions{$project} .= "$act [$context]\n";
      }
    }
  }
}
close CONTEXTS;

# Create a list of all the projects.
@projects = keys %actions;
push @projects, keys %waiting;
%unique = map { $_ => 1 } @projects;  # this eliminates duplicates
@projects = sort keys %unique;

# Open the projects file for printing.
open PROJECTS, $project_file or die "Can't open projects file: $!";
select PROJECTS;

# Print the next actions and waitings according to project.
for $project (@projects) {
  print "# $project #\n\n";
  print "Next actions:\n\n";
  print "$actions{$project}\n";
  print "Waiting for:\n\n";
  print "$waiting{$project}\n";
}
close PROJECTS;

These programs are pretty simple. About the only tricky thing in them is how “Waiting for” items are handled. I suppose I could have handled them as just another set of next actions with a context of “waiting,” but since they’re not actually things I’m going to do, I like to keep them separate.

The other LGTD command is “Print HPDA card.”

This simply calls a Perl script, “hpdaprint.pl,” which I originally wrote in July and discussed in this post. The version bundled with LGTD is slightly different, because it takes the PostScript output of html2ps and converts it to PDF before piping it to the printer.

#!/usr/bin/env perl

# Include the directory where Markdown.pm is kept.
use lib "$ENV{'TM_BUNDLE_SUPPORT'}/lib";

# The full path to the html2ps rc file for printing to index cards.
$html2psrc = "$ENV{'TM_BUNDLE_SUPPORT'}/html2psrc-hpda";

# The print command to which the PDF output is piped.
my $lpr = "lpr -o ManualFeed=True";

# The top of the HTML document.
my $top = <<'TOP';
<html>
<head>
</head>
<body>
TOP

# The bottom of the HTML document.
my $bottom = <<BOTTOM;

</body>
</html>
BOTTOM

# Slurp in the text and filter it through Markdown.
my $original;
{
  local $/;
  $original = <>;
}
use Markdown;
my $middle = Markdown::Markdown($original);

# Put page break comments before each <h1> except the first. Have to
# insert the comments from back to front to keep the offsets correct.
my @offsets = ();
my $position;
while ($middle =~ m/<h1>/g) {
  unshift @offsets, pos($middle) - 4;   # collect positions in reverse order
}
pop @offsets;                           # get rid of the last (first) one
for $position (@offsets) {
  substr($middle, $position, 0, "<!--NewPage-->\n");
}

# Form the HTML and send it through the pipeline.
my $html = $top . $middle . $bottom;
open OUT, "| html2ps -f '$html2psrc' | $ENV{'TM_LGTD_PS2PDF'} - | $lpr";
print OUT $html;
close OUT;

The program assumes you want the index cards printed from your default printer. If that’s not the case, you’ll have to change this line

my $lpr = "lpr -o ManualFeed=True";

to something like this

my $lpr = "lpr -P printer_name -o ManualFeed=True";

So that’s it. These five posts, taken together, are a complete, if not entirely coherent, description of LGTD. Since most of its functionality comes from these three Perl scripts, it could be easily adapted to work in vim, Emacs, or BBEdit. But such adaptations are, as they say in math textbooks, left as an exercise for the reader.

Tags: