Mon, 13 Feb 2006

Return from a Block


This is probably obvious to a lot of people, but it's fairly rare that I learn a new Perl programming trick these days, and I don't recall ever having seen this one before, so I thought I would make a note of it here.

Now, some people will tell you that a function should only have one exit point. If you subscribe to that opinion then this trick probably isn't for you. Personally I find multiple exit points can frequently simplify logic and avoid the need for temporary status variables or conditional blocks. They can also be abused of course, but I don't consider that a reason to ban them outright. Maybe this attitude is why I get on so well with Perl.

Anyway, sometimes you have a block from which you would like multiple exit points. If this happens to be a loop you can use last to exit from it. But if it is not a loop you might not want to create a function just for this purpose. Well, the solution turns out to be trivial. Just use last in the block. It's even documented in perldoc -f last.

Note that a block by itself is semantically identical to a loop that
executes once.  Thus "last" can be used to effect an early exit out of such
a block.

I used this for the first time in my calandar and todo script. I'll probably use it again.

[/software/perl] permanent link

Blosxom Dates


Blosxom uses the inode modification time of a file to determine the date that should be used for that file. That's all well and good, but that information can easily be lost with a stray cp command for example, so I wondered about something a little more robust. I suspect it is very likely that someone has done something like this before, and it can probably be better done as a kwiki plugin, but I decided to add a line to each file telling what date should be used, and I wrote a tiny Perl script to set the modification time to that date. Then this script is called by cron every hour and I shouldn't have to worry about editing a file to correct a type, or to add an addendum, for example.

The system line in the script is a hack. I should probably use Perl's utime function, or at least check the return value or use system LIST, but sometimes you just want to get the job done.

So, for example, the start of this file is:

Blosxom Dates
meta-markup: date 200602131230
meta-markup: kwiki

The script I wrote is:

#!/usr/bin/perl

use warnings;
use strict;

use File::Find;

find(\&wanted, <~/g/blosxom>);

sub wanted
{
    return unless /\.txt$/;
    open my $f, "<", $_ or die "Can't open $_: $!";
    while (<$f>)
    {
        if (/^meta-markup: date (.+)/)
        {
            system "touch -m -t $1 $File::Find::name";
            last;
        }
    }
}

[/unix] permanent link

Sun, 12 Feb 2006

Calendars and TODOs


I've thought for quite a while that I wanted to sort out my various TODO lists into something more general, and integrate it with a calendar of some sort.

My requirements didn't seem to extreme:

* access from any machine or some way to synchronise machines
* calendar and TODO list integrated
* let me know whats coming up and what I need to do soon

I looked at Chandler version 0.6.0, which is marked as "experimentally usable" but unfortunately I found it to be unusable. I see 0.6.1 is released now, so that might be better, but I've not tried it. But Chandler seemed to be overkill for what I wanted anyway.

So I started looking at command line applications. I experimented with ccal which seems quite nice. It's written in python and I even started hacking on it to make it do some more of what I wanted and some more of what it should have done, but in the end it just didn't do enough of what I wanted. Here's my patch, anyway:

--- /home/pjcj/utils/ccal06.py.org	2004-08-29 18:01:59.000000000 +0200
+++ /home/pjcj/utils/ccal	2006-02-12 17:38:08.230850964 +0100
@@ -182,11 +182,17 @@
                print "ccal entries:\n"
                if cal:
                        print "Calendar:\n"
-			for item in self._cal.getItems():
-				if not str(item.__class__)=="__main__.ccalItem":
-					item=ccalItem(item)
-
-				print item.entry
+			for i in range(999) :
+				viewtime = (datetime.datetime(self._cal.viewtime[0],self._cal.viewtime[1],self._cal.viewtime[2])+datetime.timedelta(days=i)).timetuple()
+				entries=self._cal.getItems(viewtime)
+				dateString = time.strftime(self._cal.dateformat,viewtime)
+				dateString += " ("+self.friendlyDateTimeDelta(datetime.datetime(viewtime[0], viewtime[1], viewtime[2]) - datetime.datetime(self._cal.localtime[0],self._cal.localtime[1],self._cal.localtime[2]))+")"
+				if entries!=None and len(entries) > 0:
+					entries.sort(lambda x, y: cmp(ccalItem(x).entry, ccalItem(y).entry))
+					for item in entries:
+						if not str(item.__class__)=="__main__.ccalItem":
+							item=ccalItem(item)
+                                                print dateString+": "+item.entry
                        print "\n"
                if todo:
                        print "Todo list:\n"
@@ -523,6 +529,7 @@
 
                                ypos+=1
                                
+				entries.sort(lambda x, y: cmp(ccalItem(x).entry, ccalItem(y).entry))
                                for entry in entries:
 
                                        

Then I started to take a look at some vim plugins hoping for better luck. I found a couple that individually seemed to do part of what I wanted. First there was VimOutliner, which will do nicely for managing my todos. Then there was calendar.vim, which will deal with the calendar part. Now all I needed to do was join them up so that todos with a date went into the calendar. I little bit of Perl sorted that out. So now I have the infrastructure. All I have to do now is use it.

#!/usr/bin/perl

# Copyright 2006, Paul Johnson (http://www.pjcj.net)

use warnings;
use strict;

use File::Find;

my $dir = <~/g/calendar>;
chdir $dir or die "Can't chdir $dir: $!";

my %todos;

my $outline = "pjcj.otl";

# Read the outline file and note any entries that have associateed dates.
open my $f, "<", $outline or die "Can't open $outline: $!";
while (<$f>)
{
    # Dates have the format YYYY-MM-DD
    push @{$todos{$2}{$1 eq "X" ? "done" : "open"}}, $3
        if /^\s*(?:\[(.)\])?\s*(?:\d*%)?\s*(20\d\d-[01]\d-[0-3]\d)\s*(.+)/;
}

my ($y, $m, $d) = (localtime)[5, 4, 3];
my $today = sprintf "%04d-%02d-%02d", $y + 1900, $m + 1, $d;
print "Today is $today\n";

sub write_calendar
{
    my ($cal) = @_;

    # Get the date from the filename.
    my ($y, $m, $d) = $cal =~ /\d+/g;
    my $date = sprintf "%04d-%02d-%02d", $y, $m, $d;

    my %entries;
    if (-e $cal)
    {
        # Read in the calendar file, ignoring any existing todos.
        open my $f, "<", $cal or die "Can't open $cal: $!";
        my @l = grep /\S/ && !/^(?:TODO|DONE) /, <$f>;
        chomp @l;
        @entries{@l} = ();

        # Delete the file - there may be nothing more to write to it.
        unlink $cal or die "Can't delete $cal: $!";
    }

    # Add the todos from the outline.
    @entries{map "TODO $_", @{$todos{$date}{open}}} = ();
    @entries{map "DONE $_", @{$todos{$date}{done}}} = ();

    {
        # If there's nothing to be done, just get out.
        last unless %entries;

        # Write the new calendar file.
        open my $f, ">", $cal or die "Can't open $cal: $!";
        print $f "$_\n" for sort keys %entries;

        # Print out what's coming up.
        # Don't print if it's in the past and there are no todos,
        # or if it's just todos and they are all done.
        last if $date lt $today && !@{$todos{$date}{open}};
        last if keys %entries eq @{$todos{$date}{done}};

        print "$date\n";
        print "  $_\n" for sort keys %entries;
    }

    # We're finished with this date.
    delete $todos{$date};
}

sub wanted
{
    # We're only interested in calendar files.
    return unless -f;
    unlink, return if -z;
    return unless /\.cal$/;

    write_calendar $_;
}

{
    no warnings "numeric";

    # Go through the calendar files in chronological order.
    find({
            wanted     => \&wanted,
            preprocess => sub { sort { $a <=> $b } @_ },
            no_chdir   => 1
         }, <20??>);
 }

# Now take the remaining todos and turn them to the calendar files.
for my $date (sort keys %todos)
{
    # Locate the calendar file from the date.
    (my $cal = $date) =~ s|-0?|/|g;
    $cal .= ".cal";

    write_calendar $cal;
}

[/software] permanent link

Fri, 03 Feb 2006

Cron and Backups


I wanted to backup my MediaWiki each night and found that a little addition to my crontab would do the trick quite nicely:

05 05 * * * mysqldump --user=wikiuser --password=s3kr1t --single-transaction --all-databases | bzip2 > /path/wiki.`date +\%Y\%m\%d`.sql.bz2

Since this is on Solaris, the % signs need to be escaped, otherwise they represent newlines. But, when I tried to use `date '+\%Y\%m\%d'` the backslashes got left in the output too. Smells like a bug to me.

I should probably backup the actual MediaWiki files too. Oh, and I'll have to clean out the backups every so often, I suppose.

[/unix] permanent link

Wed, 01 Feb 2006

Recovering SVN bdb Repositories


I looked at a few of my older SVK repositories and found that debian had upgraded bdb under me and so the repositories couldn't be read. The error message was something like:

Berkeley DB error: Berkeley DB error for filesystem /home/pjcj/g/svk/testr/db while opening environment:
DB_VERSION_MISMATCH: Database environment version mismatch: bdb: Program version 4.3 doesn't match environment version

The solution was to find a copy of svnadmin which was statically linked to the older library and use that to help upgrade the repository. While I was there, I moved the remainder of my repositories to fsfs.

I found such an svnadmin binary at uncc.org after which the sequence of commands is:

$ /path/to/static/svnadmin recover repository
$ /path/to/static/svnadmin dump repository > repository.dmp

At this point you could upgrade your bdb repository if you wanted to. I just blew it away and created a new fsfs repository. (Well, I was a little more careful.)

$ mv repository repository.bdb
$ svnadmin create repository
$ svnadmin load repository < repository.dmp

And then everything worked again.

[/revision_control] permanent link

Mirroring base


Here's how I set up my base utilities on a new machine. It's not optimal by a long shot, but it's pretty easy.

% aptitude install svk
$ cd ~
$ svk depot --init
$ svk cp http://svk.server/svn/base
$ rm -r base
$ cd g
$ svk co //base/trunk base
$ cd ..
$ ln -s g/base/* g/base/.* .

[/revision_control] permanent link

Installing SVN::Web


It's really about time I made my SVK repositories public, and as a first step towards that I decided to install SVN::Web. I'm running debian, and since the Perl SVN bindings are such a pain to install I am using the system perl to run SVK. This means that I also need to use the system perl to run SVN::Web.

The first thing I did was to install debian's mod_perl. That wasn't too hard:

# aptitude install libapache2-mod-perl2

and everything still seemed to work. Now the problem is that SVN::Web isn't packaged for debian, so the installation is going to need to be an unholy mix of debian packages and CPAN modules. I decided to try to install as many of the debian packages as I could and install the remainder from CPAN. So, the debian packages I installed were:

# aptitude install libtemplate-perl libpod-coverage-perl \
      libtest-differences-perl libmodule-build-perl libtext-diff-perl \
      libxml-rss-perl libexception-class-perl libtemplate-perl-doc \
      libapache2-request-perl libnumber-format-perl \
      libtemplate-plugin-clickable-perl libemail-find-perl

They might not all be strictly necessary.

Then I needed to install some modules from CPAN:

# perl -MCPAN -e shell
cpan> install SVN::Web
...
cpan> install Exception::Class
...
cpan> install Devel::StackTrace
...
cpan> install Template::Plugin::Number::Format
...
cpan> install Text::Diff::HTML
...
cpan> install Template::Plugin::Clickable::Email
...

I had to install Exception::Class from CPAN since the debian version was too old. Without Text::Diff::HTML the process would just sit there eating all the CPU when you asked for HTML diffs. Without Template::Plugin::Clickable::Email the error log filled up saying it wasn't there.

Then I added to /etc/apache2/sites-available/default :

<Directory /var/www/svnweb>
    AllowOverride None
    Options None
    SetHandler perl-script
    PerlHandler SVN::Web
</Directory>

<Directory /var/www/svnweb/css>
    SetHandler default-handler
</Directory>

Then I had to hack on SVN::Web.pm itself to make it work with Apache2. The diff below seems to work for me, though it might well be either overkill or underkill.

--- /usr/local/share/perl/5.8.7/SVN/Web.pm.org	2006-01-30 20:37:46.000000000 +0100
+++ /usr/local/share/perl/5.8.7/SVN/Web.pm	2006-01-30 22:50:42.000000000 +0100
@@ -861,15 +861,17 @@
 
 sub handler {
     eval "
-	use Apache::RequestRec ();
-	use Apache::RequestUtil ();
-	use Apache::RequestIO ();
-	use Apache::Response ();
-	use Apache::Const;
-	use Apache::Constants;
-        use Apache::Request;
+	use Apache2::RequestRec ();
+	use Apache2::RequestUtil ();
+	use Apache2::RequestIO ();
+	use Apache2::Response ();
+	use Apache2::Const;
+	use Apache2::Const;
+        use Apache2::Request;
     ";
 
+    die $@ if $@;
+
     my $r = shift;
     eval "$r = Apache::Request->new($r)";
     my $base = $r->location;
@@ -921,7 +923,7 @@
     }
 
     mod_perl_output($cfg, $html);
-    return &Apache::OK;
+    return &Apache2::Const::OK;
 }
 
 =head1 SEE ALSO

So I battled SVN::Web and debian and I prevailed! You can see the results at svnweb.

[/revision_control] permanent link




September 2017
Sun Mon Tue Wed Thu Fri Sat