dg-x/build.pl

200 lines
5.5 KiB
Perl
Executable file

#!/usr/bin/env perl
use Cwd qw(getcwd);
use File::Basename qw(dirname basename);
use File::Copy::Recursive qw(dircopy);
use File::Find qw(finddepth);
use File::Path qw(make_path);
use File::Slurp;
use POSIX qw(strftime);
use Text::FrontMatter::YAML;
use Text::Template;
use Text::MultiMarkdown 'markdown';
use strict;
use utf8;
use constant {
OUT_PATH => "/out",
POSTS_PATH => "/posts",
ASSETS_PATH => "/assets",
PAGES_PATH => "/pages",
TEMPLATES_PATH => "/templates",
};
# globals
my $cwd = getcwd;
my $post_dir = $cwd . POSTS_PATH;
my $pages_dir = $cwd . PAGES_PATH;
my $tmpl_dir = $cwd . TEMPLATES_PATH;
my $post_tmpl = Text::Template->new(SOURCE => "$tmpl_dir/post.html.tmpl");
my $assets_path = $cwd . ASSETS_PATH;
my $out_path = $cwd . OUT_PATH;
my $postout_path = $out_path . POSTS_PATH;
# Converts a post file to a metadata hash.
#
# Takes one argument, the path to the post file.
#
# Returns a hash with the relevant metadata (body text is stored as
# $metadata{"content"}, desired filename is stored as
# $metadata{"fname"}).
sub post_to_meta {
defined(my $fname = shift) or warn "No filename argument!";
my $fdata = read_file($fname);
my $mdfm = Text::FrontMatter::YAML->new(
document_string => $fdata
);
my $metadata = $mdfm->frontmatter_hashref;
if (chomp($mdfm->body_text) ne "") {
my $parser = Text::MultiMarkdown->new(
disable_bibliography => 1,
use_metadata => 0,
document_format => 'fragment'
);
# for very funny bits, i assure you
$mdfm->body_text =~ s{\$cn\$}{
"<a class=\"fn\">[citation needed]</a>"
}egm;
$metadata->{content} = $parser->markdown( $mdfm->body_text );
}
# HACK: Stuffing the basename in the metadata because I don't want
# to deal with hashes of hashes
$metadata->{fname} = basename($fname) unless exists($metadata->{"fname"});
$metadata;
}
# Gets an array of all posts for a directory.
#
# Takes one argument, the path to the posts directory.
#
# Returns an array of metadata hashes (see post_to_meta above for
# more on what those hashes look like).
sub all_posts_for_dir {
defined(my $postdir = shift) or warn "No directory argument!";
my @posts;
opendir(PD, $postdir) or die $!;
while (my $fname = readdir(PD)) {
next if ($fname =~ /^\.+$/);
my $post = &post_to_meta("$postdir/$fname");
push @posts, $post;
}
closedir(PD);
sort { $b->{created} cmp $a->{created} } @posts;
}
sub all_pages_for_dir {
defined(my $pagedir = shift) or warn "No directory argument!";
my @pages;
finddepth(sub {
return if ($File::Find::name =~ /^\.+$/);
my %page;
$_ = $File::Find::name;
if (/^$pagedir\/(.+).tmpl$/) {
$page{fname} = $1;
$page{tmpl} = Text::Template->new(SOURCE => $File::Find::name);
push @pages, \%page;
}
}, $pagedir);
@pages;
}
sub mkpath {
make_path( shift, { error => \my $path_err } );
if ($path_err && @$path_err) {
print "Errors occurred while making posts directory!\n";
foreach my $err (@$path_err) {
my ($path, $msg) = %$err;
if ($path eq '') {
print " $msg\n";
} else {
print " $path: $msg\n";
}
}
# effectively die
return 0;
} else {
return 1;
}
}
sub include_tmpl {
my $tmpl_name = shift;
# TODO: do we want to cache snippet-type templates like this?
my $tmpl = Text::Template->new(SOURCE => "$tmpl_dir/$tmpl_name.tmpl");
if (defined $tmpl) {
my $props = shift;
$tmpl->fill_in(HASH => { props => \$props });
} else {
"error: $tmpl_dir/$tmpl_name.tmpl is missing"
}
}
## End subroutines
# subroutines used in templates
my %utils = (
strftime => \&strftime,
include_tmpl => \&include_tmpl,
);
# make posts dir or die
mkpath($postout_path) or die "Unable to create directory.";
my @posts = all_posts_for_dir $post_dir;
print "Generating blog posts...\n";
foreach my $post (@posts) {
if (!exists $post->{content}) {
print " $post->{fname} has no content, not making a page...\n";
next;
}
print " Processing $post->{fname}...\n";
my %post_hash = %utils;
$post_hash{post} = \$post;
my $post_content = $post_tmpl->fill_in(HASH => \%post_hash);
if (defined($post_content)) {
open(POSTOUT, '>', "$postout_path/$post->{fname}.html") or die("Unable to write $post->{fname}.html: $!");
print POSTOUT $post_content;
close(POSTOUT);
} else {
die "Failed to process $post->{fname}: $Text::Template::ERROR";
}
}
print "Generating pages...\n";
my @pages = all_pages_for_dir $pages_dir;
foreach my $pg (@pages) {
mkpath(dirname("$out_path/" . $pg->{fname})) or die "Unable to create directory.";
print " Processing $pg->{fname}...\n";
my %page_hash = %utils;
$page_hash{posts} = \@posts;
my $page_content = $pg->{tmpl}->fill_in(HASH => \%page_hash);
if (defined($page_content)) {
open(PGOUT, '>', "$out_path/$pg->{fname}") or die("Unable to write $pg->{fname}: $!");
print PGOUT $page_content;
close(PGOUT);
} else {
die "Failed to process $pg->{fname}: $Text::Template::ERROR";
}
}
print "Copying static assets...\n";
local $File::Copy::Recursive::RMTrgDir = 2;
dircopy($assets_path, "$out_path/" . ASSETS_PATH) or die "Unable to copy assets: $!";
print "Done! Artifacts have been stored in $out_path.\n";