Initial commit and migration to Mercurial. mod_perl_1
authorMahlon E. Smith <mahlon@martini.nu>
Fri, 24 Jul 2009 07:39:57 -0700
changeset 0 868dae1581ff
child 1 1ae1a79094fa
Initial commit and migration to Mercurial.
Apache/OTL.pm
README
otl.js
sample.otl
themes/bg.gif
themes/otl_style.css
themes/otl_style2.css
themes/otl_style3.css
--- /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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,149 @@
+---------------------------------------------------------------------
+                                                       WHAT IS THIS?
+---------------------------------------------------------------------
+
+Vimoutliner already comes with some otl to HTML converters that work
+quite well.  I maintain a few different otl files, that are displayed
+on a internal intranet - the step of converting to HTML on every little
+change before upload was becoming mildly irritating, and countering my
+near legendary laziness.
+
+This mod_perl handler teaches apache how to pretty print otl natively.
+
+Now, I can just edit the otl files directly - skip the conversion step
+altogether, and let Apache make some delicious looking outlines.
+
+---------------------------------------------------------------------
+                                                        INSTALLATION
+---------------------------------------------------------------------
+
+First off all, make sure you have a mod_perl enabled Apache2.
+
+1) Add the following lines in your httpd.conf, or in a
+   separate otl.conf in the apache Includes directory:
+
+    -------------------------
+    PerlSwitches -I/path/to/perl/libraries
+    PerlModule Apache::OTL
+
+    <FilesMatch ".*\.otl">
+        SetHandler perl-script
+        PerlResponseHandler Apache::OTL
+    </FilesMatch>
+    -------------------------
+
+2) Put the included css at /otl_style.css in your document root.
+
+
+That's it!  Apache will now pretty-print all your otl files.
+
+---------------------------------------------------------------------
+                                                            SETTINGS
+---------------------------------------------------------------------
+
+Settings for the otl_handler are stored on the first line of the otl
+files themselves, prefixed by a colon.  See the sample.otl for an
+example settings line.  All settings are entirely optional.
+
+title
+    Type: string
+    Default: filename
+
+    The title of the OTL.  Used as a header, and the html title.
+    If this is not set, the html title is derived from the filename.
+
+
+style
+    Type: string
+    Default: /otl_style.css
+
+    A path to the css style.
+
+
+js
+    Type: string
+    Default: none
+
+    Use javascript?  If set, loads an external javascript library,
+    and calls init_page() on body load.
+    See the example 'folding' javascript included.
+
+
+divs
+    Type: boolean
+    Default: 0
+
+    Wrap each outline group in a div class called "group"
+
+
+dividers
+    Type: boolean
+    Default: 0
+
+    Separate each outline group with a horizontal rule?
+
+
+legend
+    Type: boolean
+    Default: 0
+
+    Display small legend for todo and done items?
+
+
+sort
+    Type: boolean
+    Default: 0
+
+    Show sort links?
+
+
+sorttype
+    Type: string
+    Default: none
+
+    Default sorting method.  Valid values are
+        percent
+        alpha
+
+
+sortrev
+    Type: boolean
+    Default: 0
+
+    Should we default to reverse sorting?
+
+
+counts
+    Type: boolean
+    Default: 0
+
+    Count and display sub items?
+
+
+timer
+    Type: boolean
+    Default: 0
+
+    Display how long the parser took to generate the html?
+
+
+---------------------------------------------------------------------
+                                                      INCLUDED FILES
+---------------------------------------------------------------------
+
+otl_handler.pl
+    The mod_perl code itself.
+    Feel free to modify to taste.
+
+themes/*
+    Example css.  Again, modify to taste!
+
+otl.js
+    Example (but functional!) javascript.  If you use this
+    file, your top level items will be 'clickable' - expanding
+    the sub items underneath, but not initially showing them.
+
+sample.otl
+    An example vimoutliner file, with optional settings.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/otl.js	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,109 @@
+
+// otl_handler javascript functions
+
+
+var scroll = new Array();
+var itemcount = 0;
+
+function init_page()
+{
+    if (! document.getElementById ) return false;
+
+    var spans = document.getElementsByTagName('span');
+    for (i = 0; i < spans.length; i++) {
+        var id = spans[i].getAttribute('id');
+        if (id == null || id == "") continue;
+        if (id.indexOf("itemtoplevel_") == -1) continue;
+
+        // ie doesn't support negative substr positions :\
+        // var num = id.substr(-1, 1);
+        var num = id.substr(13, 1);
+        var itemtoplevel = spans[i];
+        var itemgroup    = document.getElementById("itemgroup_" + num);
+        if (! itemtoplevel || ! itemgroup) continue;
+
+        itemcount++;
+
+        itemgroup.style.display    = 'none';
+        itemgroup.style.overflow   = 'hidden';
+        itemtoplevel.onmouseover   = function() { this.className = 'level0_over'; }
+        itemtoplevel.onmouseout    = function() { this.className = 'level0'; } 
+        itemtoplevel.onmouseup     = function() { this.className = 'level0'; toggle(this); return false; }
+        itemtoplevel.onselectstart = function() { return false; }
+
+    }
+
+    return;
+}
+
+
+function toggle(i)
+{
+    var ig = document.getElementById( i.id.replace("toplevel", "group") );
+    if (! ig ) return;
+
+    var num = ig.id.substr(10,1);
+
+    // show
+    if (ig.style.display == "" ||
+        ig.style.display == "none") {
+
+        ig.style.height = "0pt";
+        ig.style.display = 'block';
+        grow(num);
+
+        // hide others
+        for (i = 0; i != itemcount; i++) {
+           if (i != num) shrink(i);
+        }
+
+    }
+    // hide
+    else {
+        shrink(num);
+    }
+
+    return;
+}
+
+function grow(num)
+{
+    var ig = document.getElementById( "itemgroup_" + num );
+    if (! ig ) return;
+    scroll[num] = 1;
+
+    var curheight = parseInt(ig.style.height.replace("pt", ""));
+    if (curheight >= 250) {
+        ig.style.overflow = 'auto';
+        scroll[num] = 0;
+        return;
+    }
+
+    var newheight = curheight + 25 + "pt";
+    ig.style.height = newheight;
+
+    setTimeout("grow(" + num + ")", 30);
+    return;
+}
+
+function shrink(num)
+{
+    var ig = document.getElementById( "itemgroup_" + num );
+    if (! ig ) return;
+    if (scroll[num] == 1) return;
+    ig.style.overflow = 'hidden';
+
+    var curheight = parseInt(ig.style.height.replace("pt", ""));
+    if (curheight == 0) {
+        ig.style.display = 'none';
+        return;
+    }
+
+    var newheight = curheight - 50 + "pt";
+    ig.style.height = newheight;
+
+    setTimeout("shrink(" + num + ")", 30);
+    return;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample.otl	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,37 @@
+:title=Sample OTL list :style=/otl_style.css :divs=1 :dividers=0 :legend=1 :js=/otl.js :last_mod=1
+
+[_] 58% Things to get for party
+	[_] 50% Food
+		[_] Chips
+		[X] Dips
+		[X] Honey roasted peanuts
+		[_] Sausage
+	[_] 66% Party favors
+		[X] Hats
+		[X] Whistles
+		[_] Beer bong
+
+[_] 18% House projects
+	[_] 12% Paint
+		[_] 25% Buy paint and supplies
+			[_] Paint
+			[X] Brushes
+			[_] Trays
+			[_] Overalls
+		[_] 0% Rooms done
+			[_] Bathroom
+			[_] Bedroom
+				: Red?
+	[_] 20% Upgrade electrical
+		[_] 2 circuits to computer room
+		[_] 40% Get equipment
+			[X] Romex wire
+			[_] 0% Entry feed wire
+				: How much of this do I really need?
+				: I should probably go out to the street
+				: and measure stuff.
+			[_] Service meter
+			[X] Grounding rods
+			[_] Breakers
+		[_] Learn about electricity
+		[_] Don't die
Binary file themes/bg.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/otl_style.css	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,153 @@
+body
+{
+    font-family: Verdana;
+    font-size: 12px;
+    color: black;
+    background-color: #efefef;
+    margin: 30px 30px 100px 30px;
+}
+
+.legend
+{
+    margin-bottom: 10px;
+}
+
+.timer
+{
+    position: absolute;
+    top: 5;
+    right: 10;
+    color: #ccc;
+}
+
+.group
+{
+    padding: 0px;
+    background-color: white;
+    width: 600px;
+    border: 1px solid #ccc;
+    margin-bottom: 5px;
+}
+
+.header
+{
+    font-size: 24px;
+    font-variant: small-caps;
+    font-weight: bold;
+}
+
+.last_mod
+{
+    display: block;
+    border-top: 1px solid #ccc;
+    font-style: italic;
+    color: #777;
+}
+
+.counts
+{
+    font-size: 11px;
+    display: block;
+    color: #777;
+    margin-left: 30px;
+    font-style: italic;
+}
+
+.sort
+{
+    margin-bottom: 30px;
+    border-bottom: 1px solid #ccc;
+    font-weight: bold;
+}
+
+.sort a
+{
+    font-weight: normal;
+    text-decoration: none;
+    color: #777;
+}
+
+.sort a:hover
+{
+    color: black;
+}
+
+.date, .time { }
+
+.level0
+{
+    background-color: #ddd;
+    font-size: 18px;
+    font-weight: bold;
+    display: block;
+    cursor: pointer;
+    -moz-user-select: none;
+}
+
+.level0_over
+{
+    display: block;
+    background-color: #ecebe2;
+    cursor: pointer;
+}
+
+.level1
+{
+    font-size: 14px;
+    font-weight: bold;
+    margin-left: 15px;
+    color: #333;
+    display: block;
+}
+
+.level2
+{
+    font-size: 12px;
+    margin-left: 30px;
+    color: #555;
+    display: block;
+}
+
+.level3
+{
+    font-size: 10px;
+    margin-left: 45px;
+    color: #777;
+    display: block;
+}
+
+.level4
+{
+    font-size: 10px;
+    margin-left: 60px;
+    color: #aaa;
+    display: block;
+}
+
+.percent
+{
+    font-weight: bold;
+    color: #7c8ee8;
+}
+
+.todo
+{
+    background-color: #ccc;
+    padding-right: 12px;
+    margin-right: 10px;
+}
+
+.done
+{
+    background-color: #7c8ee8;
+    margin-right: 10px;
+    padding-right: 12px;
+}
+
+.comment
+{
+    font-weight: normal;
+    font-style: italic;
+    display: block;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/otl_style2.css	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,136 @@
+body
+{
+    font-family: Verdana;
+    font-size: 11px;
+    background-color: white;
+    margin: 30px 30px 100px 30px;
+}
+
+.header
+{
+    font-size: 18px;
+    font-weight: bold;
+}
+
+.last_mod
+{
+    display: block;
+    border-top: 1px solid #ccc;
+    font-style: italic;
+    color: #777;
+}
+
+.legend {}
+.date {}
+.time {}
+
+.group
+{
+    width: 50%;
+    min-width: 500px;
+    margin-bottom: 5px;
+    border-bottom: 1px solid #eee;
+}
+
+.timer
+{
+    position: absolute;
+    top: 5;
+    right: 10;
+    color: #999;
+}
+
+.counts
+{
+    color: #777;
+    margin-left: 10px;
+    font-weight: normal;
+    font-size: 10px;
+    font-style: italic;
+}
+
+.sort
+{   
+    margin-bottom: 15px;
+    font-weight: bold;
+}
+
+.sort a
+{   
+    font-weight: normal;
+    text-decoration: none;
+    color: #777;
+}
+
+.sort a:hover
+{
+    color: black;
+}
+
+.level0
+{
+    font-weight: bold;
+    cursor: pointer;
+    display: block;
+    -moz-user-select: none;
+}
+
+.level0_over
+{
+    background-color: #eee;
+    cursor: pointer;
+    display: block;
+}
+
+.level1
+{
+    display: block;
+    margin-left: 20px;
+}
+
+.level2
+{
+    display: block;
+    margin-left: 40px;
+}
+
+.level3
+{
+    display: block;
+    margin-left: 60px;
+}
+
+.level4
+{
+    display: block;
+    margin-left: 80px;
+}
+
+.percent
+{
+    color: #8193c8;
+    font-weight: bold;
+}
+
+.todo
+{
+    padding-left: 10px;
+    margin-right: 5px;
+    border: .5px solid #c88181;
+    background-color: #fbf5f5;
+}
+
+.done
+{
+    padding-left: 10px;
+    margin-right: 5px;
+    border: .5px solid #8193c8;
+    background-color: #f5f7fb;
+}
+
+.comment
+{
+    font-style: italic;
+    display: block;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/otl_style3.css	Fri Jul 24 07:39:57 2009 -0700
@@ -0,0 +1,158 @@
+body
+{
+    font-family: Verdana;
+    font-size: 12px;
+    color: black;
+    background-color: #efefef;
+    margin: 30px 30px 100px 30px;
+}
+
+.legend
+{
+    margin-bottom: 10px;
+}
+
+.timer
+{
+    position: absolute;
+    top: 5;
+    right: 10;
+    color: #ccc;
+}
+
+.group
+{
+    padding: 0px;
+    background-color: white;
+    border: 1px solid #ccc;
+    margin-bottom: 5px;
+    width: 600px;
+}
+
+.header
+{
+    font-size: 24px;
+    font-variant: small-caps;
+    font-weight: bold;
+}
+
+.last_mod
+{
+    display: block;
+    border-top: 1px solid #ccc;
+    font-style: italic;
+    color: #777;
+}
+
+.counts
+{
+    font-size: 11px;
+    display: block;
+    color: #777;
+    margin-left: 30px;
+    font-style: italic;
+}
+
+.sort
+{
+    margin-bottom: 30px;
+    border-bottom: 1px solid #ccc;
+    font-weight: bold;
+}
+
+.sort a
+{
+    font-weight: normal;
+    text-decoration: none;
+    color: #777;
+}
+
+.sort a:hover
+{
+    color: black;
+}
+
+.time, .date
+{
+    font-weight: normal;
+    font-size: 10px;
+    color: #777;
+}
+
+.level0
+{
+    background-color: #ddd;
+    font-size: 18px;
+    font-weight: bold;
+    display: block;
+    cursor: pointer;
+    -moz-user-select: none;
+}
+
+.level0_over
+{
+    display: block;
+    background-color: #ecebe2;
+    cursor: pointer;
+}
+
+.level1
+{
+    font-size: 14px;
+    font-weight: bold;
+    margin-left: 15px;
+    color: #333;
+    display: block;
+}
+
+.level2
+{
+    font-size: 12px;
+    margin-left: 30px;
+    color: #555;
+    display: block;
+}
+
+.level3
+{
+    font-size: 10px;
+    margin-left: 45px;
+    color: #777;
+    display: block;
+}
+
+.level4
+{
+    font-size: 10px;
+    margin-left: 60px;
+    color: #aaa;
+    display: block;
+}
+
+.percent
+{
+    font-weight: bold;
+    color: #7c8ee8;
+}
+
+.todo
+{
+    background-color: #ccc;
+    padding-right: 12px;
+    margin-right: 10px;
+}
+
+.done
+{
+    background-color: #7c8ee8;
+    margin-right: 10px;
+    padding-right: 12px;
+}
+
+.comment
+{
+    font-weight: normal;
+    font-style: italic;
+    display: block;
+}
+