From eb62d9f99a084f46140c6e234feb993efa0344a7 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Wed, 6 May 2015 01:21:48 +0000 Subject: [PATCH] initial commit --- index.cgi | 296 +++++++++++++++++++++++++++++++++++++ style.css | 59 ++++++++ templates/article.tmpl | 33 +++++ templates/category.tmpl | 56 +++++++ templates/commentform.tmpl | 9 ++ templates/sidebar.tmpl | 8 + 6 files changed, 461 insertions(+) create mode 100755 index.cgi create mode 100644 style.css create mode 100644 templates/article.tmpl create mode 100644 templates/category.tmpl create mode 100644 templates/commentform.tmpl create mode 100644 templates/sidebar.tmpl diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..277dbb9 --- /dev/null +++ b/index.cgi @@ -0,0 +1,296 @@ +#!/usr/bin/perl + +# Written by Nathan Wagner +# 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 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 index 0000000..228267e --- /dev/null +++ b/templates/article.tmpl @@ -0,0 +1,33 @@ + + + +[% title %] + + + +

[% blogtitle %]

+ +[% FOREACH post IN posts %] +

[% post.title %]

+

[% post.date %]

+ +[% post.content %] + +

Posted in +[% FOREACH cat IN post.categories %] +/ [% cat.name %] +[% END %] +

+ +[% IF post.comments && post.comments.length > 0 -%] +
+[% post.comments.length %] comments. +
+[%- END -%] +

Comment on this post.

+[% PROCESS commentform.tmpl %] +
+[% END %] + + + diff --git a/templates/category.tmpl b/templates/category.tmpl new file mode 100644 index 0000000..5c74e25 --- /dev/null +++ b/templates/category.tmpl @@ -0,0 +1,56 @@ + + + +[% title %] + + + +
+

[% blogtitle %]

+
+ + + +
+[% FOREACH post IN posts %] +
+

[% post.title %]

+

[% post.date %]

+ +[% post.content %] + +

Posted in +[%- FOREACH cat IN post.categories -%] + / [% cat.name %] +[%- END -%] +

+ +[% IF post.comments %] +[% post.comments.size %] comment[% IF post.comments.size != 1 %]s[% END %]. +[% END %] + +[% IF posts.size == 1 && post.comments && post.comments.size > 0 -%] +
+

Comments

+ +[% FOREACH c IN post.comments %] +

[% c.meta.Name %]

+

[% c.date %]

+[% c.content %] +[% END %] +[%- END -%] + +[% IF posts.size == 1 -%] +

Comment on this post.

+[% PROCESS commentform.tmpl %] +[%- END -%] +
+ +
+
+ +[%- END -%] + + diff --git a/templates/commentform.tmpl b/templates/commentform.tmpl new file mode 100644 index 0000000..bb9c775 --- /dev/null +++ b/templates/commentform.tmpl @@ -0,0 +1,9 @@ +
+ + + + + +
Name
Email
Comment
+ +
diff --git a/templates/sidebar.tmpl b/templates/sidebar.tmpl new file mode 100644 index 0000000..3297a11 --- /dev/null +++ b/templates/sidebar.tmpl @@ -0,0 +1,8 @@ +

Other Projects

+ +

Public Domain Software

+

+See my public domain software projects. +Mostly implemented by me, but some code taken from other public +domain software. +

-- 2.40.0