2022-02-07 06:47:31 +00:00
|
|
|
#!/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);
|
2024-02-07 18:39:09 +00:00
|
|
|
use File::Slurp;
|
2022-03-05 03:29:34 +00:00
|
|
|
use POSIX qw(strftime);
|
2024-02-07 18:39:09 +00:00
|
|
|
use Text::FrontMatter::YAML;
|
2022-02-07 06:47:31 +00:00
|
|
|
use Text::Template;
|
2024-10-06 00:24:16 +00:00
|
|
|
use FindBin;
|
|
|
|
use lib "$FindBin::RealBin/lib";
|
|
|
|
use Classy::Simple;
|
|
|
|
use Classy::Dialect;
|
2022-02-07 06:47:31 +00:00
|
|
|
|
|
|
|
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;
|
2024-10-06 00:24:16 +00:00
|
|
|
my $return_link = qq{<img class="ret-link" src="/assets/img/return.gif" />};
|
2024-06-16 04:10:34 +00:00
|
|
|
|
|
|
|
# used across a couple functions
|
2024-10-06 00:24:16 +00:00
|
|
|
my $parser = Classy::Simple->new;
|
2024-06-16 04:10:34 +00:00
|
|
|
|
|
|
|
sub make_fragment {
|
|
|
|
$parser->markdown_to_html(
|
2024-10-06 00:24:16 +00:00
|
|
|
dialects => [ 'Classy::Dialect' ],
|
|
|
|
markdown => shift,
|
2024-06-16 04:10:34 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-07 06:47:31 +00:00
|
|
|
# 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!";
|
|
|
|
|
2024-02-07 18:39:09 +00:00
|
|
|
my $fdata = read_file($fname);
|
|
|
|
my $mdfm = Text::FrontMatter::YAML->new(
|
|
|
|
document_string => $fdata
|
|
|
|
);
|
2022-02-07 06:47:31 +00:00
|
|
|
|
2024-02-07 18:39:09 +00:00
|
|
|
my $metadata = $mdfm->frontmatter_hashref;
|
|
|
|
|
2024-06-16 04:10:34 +00:00
|
|
|
chomp(my $body = $mdfm->data_text);
|
|
|
|
if ($body ne "") {
|
2022-03-22 02:01:21 +00:00
|
|
|
|
2024-02-07 18:39:09 +00:00
|
|
|
# for very funny bits, i assure you
|
2024-06-16 04:10:34 +00:00
|
|
|
$body =~ s{\[\^citation\sneeded\]}{
|
|
|
|
"<sup>[<i>citation needed</i>]</sup>"
|
2022-04-02 04:28:18 +00:00
|
|
|
}egm;
|
|
|
|
|
2024-06-16 04:10:34 +00:00
|
|
|
my $tag_attrs = qr{
|
|
|
|
(?: # Match one attr name/value pair
|
|
|
|
\s+ # There needs to be at least some whitespace
|
|
|
|
# before each attribute name.
|
|
|
|
[\w.:_-]+ # Attribute name
|
|
|
|
\s*=\s*
|
|
|
|
(?:
|
|
|
|
".+?" # "Attribute value"
|
|
|
|
|
|
|
|
|
'.+?' # 'Attribute value'
|
|
|
|
|
|
|
|
|
[^\s]+? # AttributeValue (HTML5)
|
|
|
|
)
|
|
|
|
)* # Zero or more
|
|
|
|
}x;
|
|
|
|
my $markdown_attr = qr{ \s* markdown \s* = \s* (['"]) (.*?) \1 }xs;
|
|
|
|
my $empty_tag = qr{< \w+ $tag_attrs \s* />}oxms;
|
|
|
|
|
|
|
|
use Text::Balanced qw(gen_extract_tagged);
|
|
|
|
my $extract_block = gen_extract_tagged(qr{< div $tag_attrs \s* >}oxms,
|
|
|
|
undef,
|
|
|
|
undef,
|
|
|
|
{ ignore => [$empty_tag] });
|
|
|
|
|
|
|
|
$body =~ s{
|
|
|
|
(
|
|
|
|
<div.*?</div>
|
|
|
|
)
|
|
|
|
}{
|
|
|
|
my $return = $1;
|
|
|
|
my ($tag, $remainder, $prefix, $opening_tag, $text_in_tag, $closing_tag) = $extract_block->($return);
|
|
|
|
if ($tag) {
|
|
|
|
if ($opening_tag =~ s/$markdown_attr//i) {
|
|
|
|
my $markdown = $2;
|
|
|
|
if ($markdown =~ /^(1|on|yes)$/) {
|
|
|
|
$tag = $prefix . $opening_tag . "\n"
|
|
|
|
. make_fragment($text_in_tag) . "\n" . $closing_tag;
|
|
|
|
} else {
|
|
|
|
$tag = $prefix . $opening_tag . $text_in_tag . $closing_tag;
|
|
|
|
}
|
|
|
|
$return = $tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$return;
|
|
|
|
}egmxs;
|
|
|
|
|
2024-10-06 00:24:16 +00:00
|
|
|
$metadata->{content} = make_fragment($body); # . $fn_body;
|
2022-02-07 06:47:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-22 02:01:21 +00:00
|
|
|
# HACK: Stuffing the basename in the metadata because I don't want
|
|
|
|
# to deal with hashes of hashes
|
2024-02-07 18:39:09 +00:00
|
|
|
$metadata->{fname} = basename($fname) unless exists($metadata->{"fname"});
|
2022-03-22 02:01:21 +00:00
|
|
|
|
2024-02-07 18:39:09 +00:00
|
|
|
$metadata;
|
2022-02-07 06:47:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# 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 =~ /^\.+$/);
|
2024-02-07 18:39:09 +00:00
|
|
|
my $post = &post_to_meta("$postdir/$fname");
|
|
|
|
push @posts, $post;
|
2022-02-07 06:47:31 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 00:24:16 +00:00
|
|
|
# subroutines used in templates
|
|
|
|
my %utils = (
|
|
|
|
strftime => \&strftime,
|
|
|
|
include_tmpl => \&include_tmpl,
|
|
|
|
);
|
2022-02-07 06:47:31 +00:00
|
|
|
|
|
|
|
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) {
|
2024-10-06 00:24:16 +00:00
|
|
|
my %page_hash = %utils;
|
|
|
|
my $postdata = shift;
|
|
|
|
$page_hash{props} = $postdata;
|
|
|
|
$tmpl->fill_in(HASH => \%page_hash);
|
2022-02-07 06:47:31 +00:00
|
|
|
} else {
|
|
|
|
"error: $tmpl_dir/$tmpl_name.tmpl is missing"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
## End subroutines
|
|
|
|
|
|
|
|
|
2022-03-05 03:29:34 +00:00
|
|
|
|
2022-02-07 06:47:31 +00:00
|
|
|
# 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";
|
2022-03-05 03:29:34 +00:00
|
|
|
my %post_hash = %utils;
|
|
|
|
$post_hash{post} = \$post;
|
2022-03-05 03:36:08 +00:00
|
|
|
my $post_content = $post_tmpl->fill_in(HASH => \%post_hash);
|
2022-02-07 06:47:31 +00:00
|
|
|
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";
|
2022-03-05 03:29:34 +00:00
|
|
|
my %page_hash = %utils;
|
|
|
|
$page_hash{posts} = \@posts;
|
2022-03-05 03:36:08 +00:00
|
|
|
my $page_content = $pg->{tmpl}->fill_in(HASH => \%page_hash);
|
2022-02-07 06:47:31 +00:00
|
|
|
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";
|