initial commit master
authorNathan Wagner <nw@hydaspes.if.org>
Wed, 6 May 2015 01:21:48 +0000 (01:21 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Wed, 6 May 2015 01:21:48 +0000 (01:21 +0000)
index.cgi [new file with mode: 0755]
style.css [new file with mode: 0644]
templates/article.tmpl [new file with mode: 0644]
templates/category.tmpl [new file with mode: 0644]
templates/commentform.tmpl [new file with mode: 0644]
templates/sidebar.tmpl [new file with mode: 0644]

diff --git a/index.cgi b/index.cgi
new file mode 100755 (executable)
index 0000000..277dbb9
--- /dev/null
+++ b/index.cgi
@@ -0,0 +1,296 @@
+#!/usr/bin/perl
+
+# Written by Nathan Wagner <nw@hydaspes.if.org>
+# Copyright disclaimed.  This file is in the public domain.
+
+use strict;
+use warnings;
+
+use Template;
+use Text::Markup;
+use Text::MultiMarkdown;
+use File::Find;
+use POSIX qw(strftime);
+use Digest::SHA qw(sha256_hex);
+use File::Path qw(make_path);
+
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+
+# TODO allow per category configuration.  Can just 'do .bluefin' if it exists
+# for the category
+
+my $blogtitle = `pwd`;
+chomp($blogtitle);
+$blogtitle =~ s|.+/||g;
+$blogtitle =~ s/\.[^\.]+$//;
+$blogtitle =~ s/_/ /g;
+$blogtitle = join(' ', map { ucfirst } split(/\s+/, $blogtitle));
+
+$blogtitle = 'Bad Data';
+
+my $postdir = './posts'; # where should I look for posts
+
+my $commentdir = './comments'; # where to put or look for comments
+my $commenttime = ''; # false is all allowed, positive numeric
+# is seconds after article post allowed.  negative numeric is seconds
+# after last comment allowed
+my $commentsneeddir = 0;
+# true if comment directory exists, false will create directory, if needed
+# can disable comments by creating a regular file where the comment directory
+# would be
+my $maxcommentlength = 2048; # if non-zero, maximum length in bytes of
+# a comment
+
+my $pagelimit = 10; # number of articles maximum per 'page'
+my $allowqstringpagelimit = 0; # allow query string to specify pagelimit
+# positive number is most allowed, higher will be truncated to that limit
+
+# urls like /blog/[category]/archive/[pagenum]/ will be special cased for
+# showing older posts, pagelimit is used for the breaks
+my $archivename = 'archive';
+
+my $ignorefuture = 1; # ignore posts with timestamps in the future
+
+my $sortposts = sub {
+       my $cmp;
+       $cmp = $b->{'timestamp'} <=> $a->{timestamp};
+       if (!$cmp) {
+               $cmp = $a->{'name'} cmp $b->{name};
+       }
+       return $cmp;
+};
+
+my $dateformat = '%B %d, %Y';
+my $timeformat = '%H:%m:%s';
+my $posttimeformat = '%c';
+
+# get any local config
+-f 'bluefin.cfg' && do 'bluefin.cfg';
+
+my $q = new CGI;
+
+my $get = $q->path_info;
+
+my $tt = Template->new({
+               INCLUDE_PATH => 'templates'
+       }
+);
+
+my $target = "$postdir/$get";
+my $pagenumber = 0;
+my @files;
+
+if ($q->request_method() eq 'POST') {
+       # handle a comment
+       # need form fields: article, comment
+       # remaining form fields put in metatags.
+       if (-f $target) {
+               my $text = $q->param('comment');
+               my $path = $q->path_info();
+               my $author = $q->param('name');
+               if ($maxcommentlength && length($text) > $maxcommentlength) {
+                       print $q->header(-status => 413);
+                       # TODO if -f 413.tmpl
+                       exit 0;
+               }
+               if (!-f $target) {
+                       print $q->header(-status => 403);
+                       exit 0;
+               }
+               
+               my $article = $target;
+               my $cd = "$commentdir/$path";
+               make_path($cd);
+               my $comment;
+               $comment .= "Article: $path\n";
+               $comment .= "Name: $author\n";
+               $comment .= "\n";
+               $comment .= $q->param('comment');
+               $comment .= "\n";
+               my $hash = sha256_hex($comment);
+               open(my $fh, '>', "$cd/.$hash");
+               print $fh $comment;
+               close($fh);
+               rename("$cd/.$hash", "$cd/$hash");
+               print $q->redirect($q->url(-path_info => 1));
+       } elsif (-d $target) {
+               # trying to post to a directory
+               print $q->header(-status => 403);
+               exit 0;
+       } else {
+               # trying to post to a non-existent file
+               print $q->header(-status => 404);
+               exit 0;
+
+       }
+}
+
+#my $findposts = makefinder($target);
+
+if (-d $target or -f $target) {
+       print $q->header;
+       # a category.  read in the files
+       my $page = {}; # template info
+       my @posts = (); # processed posts
+       @files = (); # raw files
+       find(\&findposts, $target); # find the post files
+       @files = sort $sortposts @files; # sort them by criteria
+       #@files = map { $_->{path} =~ s|^$target|| } @files;
+       if ($ignorefuture) {
+               my $ts = time;
+               @files = grep { $_->{timestamp} <= $ts } @files;
+       }
+
+       # process the posts we're actually going to pass to the template
+       my $start = $pagenumber * $pagelimit;
+       my $end = $start + $pagelimit;
+       my $postcount = @files;
+       if ($end > @files) {
+               $end = @files;
+       }
+       if (@files >= $start) {
+               my $url = $q->url(-relative => 1, -path_info => 1);
+               $url = $q->url();
+               @files = splice(@files, $start, $end - $start);
+               @posts = map { readpost($_->{path}, $_, $url, 1) } @files;
+       }
+
+       $page->{url} = $q->url(-rewrite => 0, -path => 0);
+       $page->{totalposts} = $postcount;
+       $page->{postpages} = int($postcount/$pagelimit) + $postcount % $pagelimit == 0 ? 0 : 1;
+       $page->{blogtitle} = $blogtitle;
+       $page->{posts} = \@posts;
+       $tt->process('category.tmpl', $page) or die $tt->error;
+} elsif (-f $target) {
+       my $page = {};
+       my $post = readpost($target);
+       $page->{blogtitle} = $blogtitle;
+       $page->{posts} = [ $post ];
+
+       # a single article
+       $tt->process('article.tmpl', $page);
+}
+exit 0;
+
+sub readfile {
+       my ($path) = @_;
+       if (!-f $path) {
+               return ();
+       }
+       my $page = {};
+       $page->{'timestamp'} = (stat($path))[9];
+       my @timeinfo = localtime($page->{'timestamp'});
+       $page->{'timeinfo'} = \@timeinfo;
+       $page->{'postdate'} = strftime($dateformat, @timeinfo);
+       $page->{'posttime'} = strftime($timeformat, @timeinfo);
+       $page->{'date'} = strftime($posttimeformat, @timeinfo);
+
+       my ($fh);
+       open $fh, $path;
+       local $/ = "";
+       my @lines = <$fh>;
+       if ($lines[0] and $lines[0] =~ m/^.+:\s*.+/m) {
+               my %hdrinfo = ($lines[0] =~ m/^(.+):\s*(.+)/mg);
+               $page->{'meta'} = \%hdrinfo;
+               $page->{'title'} = $page->{'meta'}{'Title'};
+
+               shift @lines;
+       }
+
+       my $lines = join('', @lines);
+       my $md = Text::MultiMarkdown->new();
+       my $html = $md->markdown($lines);
+       close $fh;
+       $page->{'content'} = $html;
+
+       return $page;
+}
+
+sub find_files {
+       my @paths = @_;
+       my @files = ();
+       my $asof = time;
+
+       my $find = sub { push @files, $File::Find::name if ( (-f $_) && (!m/^\./) )};
+       my $get = sub { wantarray ? @files : [ @files ] };
+       find($find, @paths);
+       return $get->();
+}
+
+sub readpost {
+       my ($path, $xinfo, $url, $comments) = @_;
+       my %page;
+
+       my $page = readfile($path);
+       return () unless $page;
+
+       $page->{'url'} = $url . '/' . $xinfo->{lpath};
+
+       if (!$page->{'title'}) {
+               my $t = $xinfo->{name};
+               $t =~ s/\.[^\.]+$//;
+               $t =~ s/_/ /g;
+               $t = join(' ', map { ucfirst } split(/\s+/, $t));
+               $page->{'title'} = $t;
+       }
+
+       my $cd = "$commentdir/" . $xinfo->{lpath};
+       if ($comments) {
+               if (-d $cd) {
+                       my @comments = map { readfile($_) } find_files($cd);
+                       @comments = sort {
+                               my $cmp;
+                               $cmp = $a->{'timestamp'} <=> $b->{timestamp};
+                               if (!$cmp) {
+                                       $cmp = $a->{'name'} <=> $b->{name};
+                               }
+                               return $cmp;
+                               } @comments;
+
+                       $page->{comments} = \@comments;
+               } else {
+                       $page->{comments} = [];
+               }
+       }
+
+       $page->{'xinfo'} = $xinfo;
+
+       my @catpath = split(m|/|, $xinfo->{lpath});
+       pop @catpath;
+       $page->{'catpath'} = \@catpath;
+       my $caturl = $q->url(-rewrite => 0) . '/../';
+       my @caturls = ();
+       foreach my $cat (@catpath) {
+               $caturl .= "$cat/";
+               push @caturls, { name => $cat, url => $caturl };
+       }
+       
+       $page->{'categories'} = \@caturls;
+
+       return $page;
+}
+
+sub findposts {
+       my %fileinfo;
+       return unless -f;
+       return if m/^\./; # skip dotfiles
+       $fileinfo{'stat'} = [ stat $_ ];
+       $fileinfo{'timestamp'} = (stat _)[9];
+       $fileinfo{'name'} = $_;
+       $fileinfo{'dir'} = $File::Find::dir;
+       $fileinfo{'path'} = $File::Find::name;
+       $fileinfo{'lpath'} = $File::Find::name;
+       $fileinfo{'lpath'} =~ s|^$postdir/*||;
+       push @files, \%fileinfo;
+}
+
+__END__
+my $parser = Text::Markup->new(
+       default_format   => 'multimarkdown',
+       default_encoding => 'UTF-8',
+);
+
+#my $html = $parser->parse(file => $file);
+
+print $html;
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..83d3fa4
--- /dev/null
+++ b/style.css
@@ -0,0 +1,59 @@
+body {
+               /* background-color: #181818; */
+               background-color: #000000;
+       color: #E8E8E8;
+               margin-left: 1cm;
+               margin-right: 1cm;
+       /* max-width: 80ex; */
+       text-align: justify;
+       font-family: "Avenir Next", Helvetica, sans-serif;
+       /* font: "Iowan Old Style", "Avenir Next", "Source Code Pro"; */
+}
+
+body div#posts {
+       max-width: 80ex;
+       text-align: justify;
+}
+
+article { max-width: 70ex; text-align: justify; }
+
+.sidebar { float: right;
+               margin-right: 1mm;
+               margin-left: 1cm;
+               border: 1px;
+               background-color: #777777;
+               padding-left: 5mm;
+               padding-right: 5mm;
+               max-width: 40ex;
+       border-style: solid;
+       font-size: small;
+}
+
+.sidebar a {
+       text-decoration: underline;
+       color: #0000FF;
+}
+
+header h1 {
+               text-align: center;
+       border: double;
+       font-family: "Iowan Old Style", serif;
+}
+
+a { text-decoration: none; color: #F0F0F0; }
+
+a.category {
+       color: #6060FF;
+}
+
+
+article h2 { border: 0px; padding: 0px; margin: 0px; font-family: serif; }
+article a.title { color: #ff0000; }
+
+p.date {
+       font-size: small;
+       font-style: italic;
+       border: 0px;
+       padding: 0px;
+       margin: 0px;
+}
diff --git a/templates/article.tmpl b/templates/article.tmpl
new file mode 100644 (file)
index 0000000..228267e
--- /dev/null
@@ -0,0 +1,33 @@
+<html>
+<head>
+<link REL="StyleSheet" HREF="[% url %]/../style.css" TYPE="text/css" MEDIA="screen">
+<title>[% title %]</title>
+</head>
+
+<body>
+<h1 class="blogtitle"><a href="/~nw/blog/">[% blogtitle %]</a></h1>
+
+[% FOREACH post IN posts %]
+<h2 class="articletitle"><a href="[% post.url %]" class="title">[% post.title %]</a></h2>
+<p class="date">[% post.date %]</p>
+
+[% post.content %]
+
+<p>Posted in
+[% FOREACH cat IN post.categories %]
+/ <a href="[% cat.url %]">[% cat.name %]</a>
+[% END %]
+</p>
+
+[% IF post.comments && post.comments.length > 0 -%]
+<div id="#comments">
+[% post.comments.length %] comments.
+</div>
+[%- END -%]
+<h3>Comment on this post.</h3>
+[% PROCESS commentform.tmpl %]
+<hr>
+[% END %]
+
+</body>
+</html>
diff --git a/templates/category.tmpl b/templates/category.tmpl
new file mode 100644 (file)
index 0000000..5c74e25
--- /dev/null
@@ -0,0 +1,56 @@
+<html>
+<head>
+<link REL="StyleSheet" HREF="[% url %]/../style.css" TYPE="text/css" MEDIA="screen">
+<title>[% title %]</title>
+</head>
+
+<body>
+<header>
+<h1 class="blogtitle"><a href="/~nw/blog/">[% blogtitle %]</a></h1>
+</header>
+
+<div class="sidebar">
+[% PROCESS sidebar.tmpl %]
+</div>
+
+<div class="posts" id="posts">
+[% FOREACH post IN posts %]
+<article>
+<h2 class="title"><a href="[% post.url %]" class="title">[% post.title %]</a></h2>
+<p class="date">[% post.date %]</p>
+
+[% post.content %]
+
+<p>Posted in
+[%- FOREACH cat IN post.categories -%]
+ / <a class="category" href="[% cat.url %]">[% cat.name %]</a>
+[%- END -%]
+</p>
+
+[% IF post.comments %]
+<a href="[% post.url %]#comments">[% post.comments.size %] comment[% IF post.comments.size != 1 %]s[% END %]</a>.
+[% END %]
+
+[% IF posts.size == 1 && post.comments && post.comments.size > 0 -%]
+<div>
+<h3 id="comments">Comments</h3>
+
+[% FOREACH c IN post.comments %]
+<h4>[% c.meta.Name %]</h4>
+<p>[% c.date %]</p>
+[% c.content %]
+[% END %]
+[%- END -%]
+
+[% IF posts.size == 1 -%]
+<h3>Comment on this post.</h3>
+[% PROCESS commentform.tmpl %]
+[%- END -%]
+<hr>
+
+</article>
+</div>
+
+[%- END -%]
+</body>
+</html>
diff --git a/templates/commentform.tmpl b/templates/commentform.tmpl
new file mode 100644 (file)
index 0000000..bb9c775
--- /dev/null
@@ -0,0 +1,9 @@
+<form name="commentform" method="post">
+<table>
+<tr><th>Name</th><td><input name="name" type="text"/></td></tr>
+<tr><th>Email</th><td><input name="email" type="text"/></td></tr>
+<tr><th colspan="1">Comment</th></tr>
+<tr><td colspan="2"><textarea name="comment" type="textarea" rows="10" cols="72"></textarea></td></tr>
+</table>
+<input type="submit" value="Submit Comment" />
+</form>
diff --git a/templates/sidebar.tmpl b/templates/sidebar.tmpl
new file mode 100644 (file)
index 0000000..3297a11
--- /dev/null
@@ -0,0 +1,8 @@
+<h2>Other Projects</h2>
+
+<h3>Public Domain Software</h3>
+<p>
+See my <a href="https://pd.if.org/git/">public domain software projects</a>.
+Mostly implemented by me, but some code taken from other public
+domain software.
+</p>