# HG changeset patch # User Mahlon E. Smith # Date 1248446397 25200 # Node ID 868dae1581ff74e44b997c1805d258c83a4156ba Initial commit and migration to Mercurial. diff -r 000000000000 -r 868dae1581ff Apache/OTL.pm --- /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 = ; # 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(< + + + $title + +EHTML + + if ($opt{js}) { + $r->print( + ' ' x 8, + "\n", + ' ' x 4, "\n", + "\n", + ); + } else { + $r->print(< + +EHTML + } + + $r->print("
$opt{title}
\n") if $opt{title}; + $r->print("
Last modified: $mtime
\n") if $opt{last_mod}; + if ($opt{legend}) { + $r->print(< +  Item completed
+  Item is incomplete
+ +EHTML + } + if ($opt{sort}) { + my %sorts = ( + alpha => 'alphabetical', + percent => 'percentages', + ); + $r->print("
Sort: \n"); + foreach (sort keys %sorts) { + if ($opt{sorttype} eq $_ && $opt{sortrev}) { + $r->print("$sorts{$_} "); + } elsif ($opt{sorttype} eq $_ && ! $opt{sortrev}) { + $r->print("$sorts{$_} "); + } else { + $r->print("$sorts{$_} "); + } + } + $r->print("
\n"); + } + + my $bc = 0; + foreach my $block ( sort { sorter(\%opt, \%re) } @blocks ) { + # separate outline items + my @items = split /\n/, $block; + $r->print("
\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'; + $_ .= " $subs $itmstr, $subsubs $sitmstr"; + } + s/^:// if ! $level; + + if ($opt{js}) { + s#(.+)#$1# if $lc == 0; + $r->print("\n") if $lc == 1; + } + + s#$re{'time'}#$1#g if /$re{'time'}/; + s#$re{date}#$1#g if /$re{date}/; + s#$re{percent}#$1 $2%# if /$re{percent}/; + s#$re{todo}# # if /$re{todo}/; + s#$re{done}# # if /$re{done}/; + s#$re{comment}#$1# if /$re{comment}/; + s#$re{line_wo_tabs}#$1#; + + $r->print("$_\n"); + $lc++; + } + $r->print("\n") if $opt{js}; + $r->print("
\n") if $opt{divs}; + $r->print("


\n") if $opt{dividers}; + $r->print("

\n") unless $opt{divs} || $opt{dividers}; + $bc++; + } + + my $t1 = Time::HiRes::gettimeofday; + my $td = sprintf("%0.3f", $t1 - $t0); + $r->print("
OTL parsed in $td secs
") if $opt{timer}; + $r->print(< + +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; diff -r 000000000000 -r 868dae1581ff README --- /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 + + + SetHandler perl-script + PerlResponseHandler Apache::OTL + + ------------------------- + +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. + + diff -r 000000000000 -r 868dae1581ff otl.js --- /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; +} + + diff -r 000000000000 -r 868dae1581ff sample.otl --- /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 diff -r 000000000000 -r 868dae1581ff themes/bg.gif Binary file themes/bg.gif has changed diff -r 000000000000 -r 868dae1581ff themes/otl_style.css --- /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; +} + diff -r 000000000000 -r 868dae1581ff themes/otl_style2.css --- /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; +} + diff -r 000000000000 -r 868dae1581ff themes/otl_style3.css --- /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; +} +