Apache/OTL.pm
changeset 0 868dae1581ff
child 1 1ae1a79094fa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Apache/OTL.pm	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,232 @@
+#!/usr/bin/perl
+
+package Apache::OTL;
+use strict;
+use Apache2::Const qw/ DECLINED OK /;
+use Time::HiRes qw/ gettimeofday /;
+
+sub handler
+{
+    my $r = shift;
+    my $VERSION = '0.4';
+    my $t0 = Time::HiRes::gettimeofday;
+    my (
+        $file,          # the absolute file path
+        $title,         # the file's title
+        $uri,           # the file uri
+        %re,            # a hash of pre compiled regular expressions
+        $data,          # file contents
+        %opt,           # options from the otl file
+        @blocks,        # todo groupings
+        $mtime,         # last modification time of otl file
+        %get,           # get arguments (sorting, etc)
+    );
+
+    return DECLINED unless $r->method() eq 'GET';
+    ($file, $uri) = ($r->filename, $r->uri);
+    return DECLINED unless -e $file;
+    $mtime = localtime( (stat(_))[9] );
+
+    %get = $r->args;
+
+    %re =
+    (
+      title   => qr/(?:.+)?\/(.+).otl$/i,
+      percent => qr/(\[.\]) (\d+)%/,
+      todo    => qr/(\[_\]) /,
+      done    => qr/(\[X\]) /,
+      comment => qr/^(?:\t+)?:(.+)/,
+      time    => qr/(\d{2}:\d{2}:\d{2})/,
+      date    => qr/(\d{2,4}-\d{2}-\d{2})/,
+      subitem => qr/^\t(?!\t)/,
+      line_wo_tabs => qr/^(?:\t+)?(.+)/,
+      linetext => qr/^(?:\[.\] (?:\d+%)?)? (.+)/,
+    );
+
+    open OTL, "$file"
+      || ( $r->log_error("Unable to read $file: $!") && return DECLINED );
+    do {
+        local $/ = undef;
+        $data = <OTL>;  # shlorp
+    };
+    close OTL;
+
+    # just spit out the plain otl if requested.
+    if ($get{show} eq 'source') {
+        $r->content_type('text/plain');
+        $r->print( $data );
+        return OK;
+    }           
+
+    # divide each outline into groups
+    # skip blocks that start with a comment '#'
+    @blocks = grep { $_ !~ /^\#/ } split /\n\n+/, $data;
+
+    # get optional settings and otl title
+    {
+        my $settings = shift @blocks;
+        if ($settings =~ $re{comment}) {
+            %opt = map { split /=/ } split /\s?:/, $settings;
+        }
+        
+        # if the first group wasn't a comment,
+        # we probably just aren't using a settings
+        # line.  push the group back into place.
+        else {
+            unshift @blocks, $settings;
+        }
+    }
+
+    # GET args override settings
+    $opt{$_} = $get{$_} foreach keys %get;
+
+    # set title (fallback to file uri)
+    $title =
+        $opt{title}
+      ? $opt{title}
+      : $1 if $uri =~ $re{title};
+
+    $opt{style} ||= '/otl_style.css';
+
+    $r->content_type('text/html');
+    $r->print(<<EHTML);
+<html>
+    <!--
+        generated by otl_handler $VERSION
+        Mahlon E. Smith <mahlon\@spime.net>
+
+        http://www.vimoutliner.org/
+    -->
+    <head>
+        <title>$title</title>
+        <link href="$opt{style}" rel="stylesheet" media="screen" type="text/css">
+EHTML
+
+    if ($opt{js}) {
+        $r->print(
+            ' ' x 8,
+            "<script type=\"text/javascript\" language=\"JavaScript\" src=\"$opt{js}\"></script>\n",
+            ' ' x 4, "</head>\n",
+            "<body onLoad=\"init_page()\">\n",
+        );
+    } else {
+        $r->print(<<EHTML);
+    </head>
+    <body>
+EHTML
+    }
+
+    $r->print("<div class=\"header\">$opt{title}</div>\n") if $opt{title};
+    $r->print("<div class=\"last_mod\">Last modified: $mtime</div>\n") if $opt{last_mod};
+    if ($opt{legend}) {
+        $r->print(<<EHTML);
+<div class="legend">
+<span class="done">&nbsp;</span> Item completed<br />
+<span class="todo">&nbsp;</span> Item is incomplete<br />
+</div>
+EHTML
+    }
+    if ($opt{sort}) {
+        my %sorts = (
+            alpha   => 'alphabetical',
+            percent => 'percentages',
+        );
+        $r->print("<div class=\"sort\">Sort: \n");
+        foreach (sort keys %sorts) {
+            if ($opt{sorttype} eq $_ && $opt{sortrev}) {
+                $r->print("<a href=\"$uri?sorttype=$_\">$sorts{$_}</a>&nbsp;");
+            } elsif ($opt{sorttype} eq $_ && ! $opt{sortrev}) {
+                $r->print("<a href=\"$uri?sorttype=$_&sortrev=1\">$sorts{$_}</a>&nbsp;");
+            } else {
+                $r->print("<a href=\"$uri?sorttype=$_\">$sorts{$_}</a>&nbsp;");
+            }
+        }
+        $r->print("</div>\n");
+    }
+
+    my $bc = 0;
+    foreach my $block ( sort { sorter(\%opt, \%re) } @blocks ) {
+        # separate outline items
+        my @items = split /\n/, $block;
+        $r->print("<div class=\"group\">\n") if $opt{divs};
+        my $lc = 0;
+        
+        # get item counts
+        my ($subs, $comments, $subsubs);
+        if ($opt{counts}) {
+            foreach (@items) {
+                if (/$re{comment}/) {
+                    $comments++;
+                } elsif (/$re{subitem}/) {
+                    $subs++;
+                }
+            }
+            $subsubs = (scalar @items - 1) - $subs - $comments;;
+        }
+
+        # parse
+        foreach (@items) {
+            next if /^\#/;
+            my $level = tr/\t/\t/ || 0;
+            next unless /\w/;
+
+            # append counts
+            if ($lc == 0 && $opt{counts} && $_ !~ $re{comment}) {
+                my $itmstr  = $subs == 1    ? 'item'    : 'items';
+                my $sitmstr = $subsubs == 1 ? 'subitem' : 'subitems';
+                $_ .= " <span class=\"counts\">$subs $itmstr, $subsubs $sitmstr</span>";
+            }
+            s/^:// if ! $level;
+
+            if ($opt{js}) { 
+                s#(.+)#<span id=\"itemtoplevel_$bc\">$1</span># if $lc == 0;
+                $r->print("<span id=\"itemgroup_$bc\">\n")      if $lc == 1;
+            }
+
+            s#$re{'time'}#<span class="time">$1</span>#g        if /$re{'time'}/;
+            s#$re{date}#<span class="date">$1</span>#g          if /$re{date}/;
+            s#$re{percent}#$1 <span class="percent">$2%</span># if /$re{percent}/;
+            s#$re{todo}#<span class="todo">&nbsp;</span>#       if /$re{todo}/;
+            s#$re{done}#<span class="done">&nbsp;</span>#       if /$re{done}/;
+            s#$re{comment}#<span class="comment">$1</span>#     if /$re{comment}/;
+            s#$re{line_wo_tabs}#<span class="level$level">$1</span>#;
+
+            $r->print("$_\n");
+            $lc++;
+        }
+        $r->print("</span>\n")            if $opt{js};
+        $r->print("</div>\n")             if $opt{divs};
+        $r->print("<br /><hr /><br />\n") if $opt{dividers};
+        $r->print("<br /><br />\n") unless $opt{divs} || $opt{dividers};
+        $bc++;
+    }
+
+    my $t1 = Time::HiRes::gettimeofday;
+    my $td = sprintf("%0.3f", $t1 - $t0);
+    $r->print("<div class=\"timer\">OTL parsed in $td secs</div>") if $opt{timer};
+    $r->print(<<EHTML);
+    </body>
+</html>
+EHTML
+
+    return OK;
+}
+
+sub sorter
+{
+    my ($opt, $re) = @_;
+    return 0 unless $opt->{sorttype};
+    my ($sa, $sb);
+    if ($opt->{sorttype} eq 'percent') {
+        $sa = $2 if $a =~ $re->{percent};
+        $sb = $2 if $b =~ $re->{percent};
+        return $opt->{sortrev} ? $sb <=> $sa : $sa <=> $sb;
+    }
+    else {
+        $sa = $1 if $a =~ $re->{linetext};
+        $sb = $1 if $b =~ $re->{linetext};
+        return $opt->{sortrev} ? $sb cmp $sa : $sa cmp $sb;
+    }
+}
+
+1;