3 # Written by Nathan Wagner <nw@hydaspes.if.org>
4 # Copyright disclaimed. This file is in the public domain.
11 use Text::MultiMarkdown;
13 use POSIX qw(strftime);
14 use Digest::SHA qw(sha256_hex);
15 use File::Path qw(make_path);
18 use CGI::Carp qw(fatalsToBrowser);
20 # TODO allow per category configuration. Can just 'do .bluefin' if it exists
23 my $blogtitle = `pwd`;
25 $blogtitle =~ s|.+/||g;
26 $blogtitle =~ s/\.[^\.]+$//;
27 $blogtitle =~ s/_/ /g;
28 $blogtitle = join(' ', map { ucfirst } split(/\s+/, $blogtitle));
30 $blogtitle = 'Bad Data';
32 my $postdir = './posts'; # where should I look for posts
34 my $commentdir = './comments'; # where to put or look for comments
35 my $commenttime = ''; # false is all allowed, positive numeric
36 # is seconds after article post allowed. negative numeric is seconds
37 # after last comment allowed
38 my $commentsneeddir = 0;
39 # true if comment directory exists, false will create directory, if needed
40 # can disable comments by creating a regular file where the comment directory
42 my $maxcommentlength = 2048; # if non-zero, maximum length in bytes of
45 my $pagelimit = 10; # number of articles maximum per 'page'
46 my $allowqstringpagelimit = 0; # allow query string to specify pagelimit
47 # positive number is most allowed, higher will be truncated to that limit
49 # urls like /blog/[category]/archive/[pagenum]/ will be special cased for
50 # showing older posts, pagelimit is used for the breaks
51 my $archivename = 'archive';
53 my $ignorefuture = 1; # ignore posts with timestamps in the future
57 $cmp = $b->{'timestamp'} <=> $a->{timestamp};
59 $cmp = $a->{'name'} cmp $b->{name};
64 my $dateformat = '%B %d, %Y';
65 my $timeformat = '%H:%m:%s';
66 my $posttimeformat = '%c';
68 # get any local config
69 -f 'bluefin.cfg' && do 'bluefin.cfg';
73 my $get = $q->path_info;
75 my $tt = Template->new({
76 INCLUDE_PATH => 'templates'
80 my $target = "$postdir/$get";
84 if ($q->request_method() eq 'POST') {
86 # need form fields: article, comment
87 # remaining form fields put in metatags.
89 my $text = $q->param('comment');
90 my $path = $q->path_info();
91 my $author = $q->param('name');
92 if ($maxcommentlength && length($text) > $maxcommentlength) {
93 print $q->header(-status => 413);
98 print $q->header(-status => 403);
102 my $article = $target;
103 my $cd = "$commentdir/$path";
106 $comment .= "Article: $path\n";
107 $comment .= "Name: $author\n";
109 $comment .= $q->param('comment');
111 my $hash = sha256_hex($comment);
112 open(my $fh, '>', "$cd/.$hash");
115 rename("$cd/.$hash", "$cd/$hash");
116 print $q->redirect($q->url(-path_info => 1));
117 } elsif (-d $target) {
118 # trying to post to a directory
119 print $q->header(-status => 403);
122 # trying to post to a non-existent file
123 print $q->header(-status => 404);
129 #my $findposts = makefinder($target);
131 if (-d $target or -f $target) {
133 # a category. read in the files
134 my $page = {}; # template info
135 my @posts = (); # processed posts
136 @files = (); # raw files
137 find(\&findposts, $target); # find the post files
138 @files = sort $sortposts @files; # sort them by criteria
139 #@files = map { $_->{path} =~ s|^$target|| } @files;
142 @files = grep { $_->{timestamp} <= $ts } @files;
145 # process the posts we're actually going to pass to the template
146 my $start = $pagenumber * $pagelimit;
147 my $end = $start + $pagelimit;
148 my $postcount = @files;
152 if (@files >= $start) {
153 my $url = $q->url(-relative => 1, -path_info => 1);
155 @files = splice(@files, $start, $end - $start);
156 @posts = map { readpost($_->{path}, $_, $url, 1) } @files;
159 $page->{url} = $q->url(-rewrite => 0, -path => 0);
160 $page->{totalposts} = $postcount;
161 $page->{postpages} = int($postcount/$pagelimit) + $postcount % $pagelimit == 0 ? 0 : 1;
162 $page->{blogtitle} = $blogtitle;
163 $page->{posts} = \@posts;
164 $tt->process('category.tmpl', $page) or die $tt->error;
165 } elsif (-f $target) {
167 my $post = readpost($target);
168 $page->{blogtitle} = $blogtitle;
169 $page->{posts} = [ $post ];
172 $tt->process('article.tmpl', $page);
182 $page->{'timestamp'} = (stat($path))[9];
183 my @timeinfo = localtime($page->{'timestamp'});
184 $page->{'timeinfo'} = \@timeinfo;
185 $page->{'postdate'} = strftime($dateformat, @timeinfo);
186 $page->{'posttime'} = strftime($timeformat, @timeinfo);
187 $page->{'date'} = strftime($posttimeformat, @timeinfo);
193 if ($lines[0] and $lines[0] =~ m/^.+:\s*.+/m) {
194 my %hdrinfo = ($lines[0] =~ m/^(.+):\s*(.+)/mg);
195 $page->{'meta'} = \%hdrinfo;
196 $page->{'title'} = $page->{'meta'}{'Title'};
201 my $lines = join('', @lines);
202 my $md = Text::MultiMarkdown->new();
203 my $html = $md->markdown($lines);
205 $page->{'content'} = $html;
215 my $find = sub { push @files, $File::Find::name if ( (-f $_) && (!m/^\./) )};
216 my $get = sub { wantarray ? @files : [ @files ] };
222 my ($path, $xinfo, $url, $comments) = @_;
225 my $page = readfile($path);
226 return () unless $page;
228 $page->{'url'} = $url . '/' . $xinfo->{lpath};
230 if (!$page->{'title'}) {
231 my $t = $xinfo->{name};
234 $t = join(' ', map { ucfirst } split(/\s+/, $t));
235 $page->{'title'} = $t;
238 my $cd = "$commentdir/" . $xinfo->{lpath};
241 my @comments = map { readfile($_) } find_files($cd);
244 $cmp = $a->{'timestamp'} <=> $b->{timestamp};
246 $cmp = $a->{'name'} <=> $b->{name};
251 $page->{comments} = \@comments;
253 $page->{comments} = [];
257 $page->{'xinfo'} = $xinfo;
259 my @catpath = split(m|/|, $xinfo->{lpath});
261 $page->{'catpath'} = \@catpath;
262 my $caturl = $q->url(-rewrite => 0) . '/../';
264 foreach my $cat (@catpath) {
266 push @caturls, { name => $cat, url => $caturl };
269 $page->{'categories'} = \@caturls;
277 return if m/^\./; # skip dotfiles
278 $fileinfo{'stat'} = [ stat $_ ];
279 $fileinfo{'timestamp'} = (stat _)[9];
280 $fileinfo{'name'} = $_;
281 $fileinfo{'dir'} = $File::Find::dir;
282 $fileinfo{'path'} = $File::Find::name;
283 $fileinfo{'lpath'} = $File::Find::name;
284 $fileinfo{'lpath'} =~ s|^$postdir/*||;
285 push @files, \%fileinfo;
289 my $parser = Text::Markup->new(
290 default_format => 'multimarkdown',
291 default_encoding => 'UTF-8',
294 #my $html = $parser->parse(file => $file);