website/lib/DB.rakumod

165 lines
5.4 KiB
Raku

use v6.e.PREVIEW;
#| Post database
unit module DB;
use Pandoc;
use JSON::Class:auth<zef:vrurg>;
use XML;
use XQ;
use DB::Post;
use DB::BlogMeta;
use DB::MarkdownPost;
use DB::IdrisPost;
use DB::PlaceholderPost;
use Atom;
use Config;
subset PostTypes where MarkdownPost:D | IdrisPost:D | PlaceholderPost:D;
#| The top level posts database
class PostDB {
#| The metadata for the blog
has BlogMeta:D $.meta is required;
#| A mapping from post ids to posts
# has %.posts is Posts;
has %.posts{Int} of PostTypes = %();
#| The post id to use for placeholder posts
has Int $.placeholder-id = 0;
#| Get the next unused post ID
method next-post-id(--> Int) {
if %!posts.elems > 0 {
%!posts.keys.max + 1
} else {
0
}
}
#| Insert a new post to the DB, returning its id
method insert-post(PostTypes $post --> Int) {
my $id = self.next-post-id;
%!posts{$id} = $post;
$id
}
#| Initialize a new database
method init(BlogMeta:D $meta --> PostDB:D) {
my %posts{Int} of PostTypes = %();
%posts{0} = PlaceholderPost.empty;
PostDB.new(
meta => $meta,
posts => %posts,
)
}
#| Write a database to a directory
method write(IO::Path:D $dir) {
my $posts-dir = $dir.add('posts/');
# Make sure directory structrue exists
mkdir $dir unless $dir.e;
mkdir $posts-dir unless $posts-dir.e;
# Write out metadata
# TODO: Track changes and only write changed files
$dir.add('meta.json').spurt: $!meta.to-json;
# Write out posts (ids are the filename)
for %!posts.kv -> $key, $value {
$posts-dir.add("$key.json").spurt: $value.to-json;
}
}
#| Render the site to the provided output directory
method render(IO::Path:D $out-dir, Config:D :$config = Config.new) {
## Consistency checks
# Check to make sure all the slugs are unique
my @all-the-slugs = %!posts.values.map(*.all-slugs).flat;
die "Duplicate slug detected"
unless @all-the-slugs.unique.elems == @all-the-slugs.elems;
## Rendering
my $posts = $out-dir.add('posts/');
my $by-id = $posts.add('by-id/');
my $by-slug = $posts.add('by-slug/');
# Make sure the directory structure exists
mkdir $out-dir unless $out-dir.e;
mkdir $posts unless $posts.e;
mkdir $by-id unless $by-id.e;
mkdir $by-slug unless $by-slug.e;
# Render all the posts and make symlinks
for %!posts.kv -> $id, $post {
my $html = $config.generate-post: $post, $!meta;
my $id-path = $by-id.add: "$id.html";
$id-path.spurt: $html;
for $post.all-slugs -> $slug {
# remove the symlink if it already exists
my $slug-path = $by-slug.add: "$slug.html";
$slug-path.unlink if $slug-path.l;
$id-path.symlink: $slug-path;
}
}
# Render the index
$out-dir.add('index.html').spurt: $config.generate-index(self);
# Render the archive
$out-dir.add('archive.html').spurt: $config.generate-archive(self);
# Symlink the about article
my $about-path = $out-dir.add('about.html');
$about-path.unlink if $about-path.l;
$by-id.add("{$!meta.about-id}.html").symlink: $about-path;
# Generate the tags pages
my @tags = %!posts.values.map(*.tags).flat.unique;
$out-dir.add('tags.html').spurt: $config.generate-tags-page(self, @tags);
my $tags-dir = $out-dir.add('tags/');
mkdir $tags-dir unless $tags-dir.e;
for @tags -> $tag {
$tags-dir.add("$tag.html").spurt:
$config.generate-tag-page(self, $tag);
}
# Render the rss/atom feed
my $atom-path = $out-dir.add('atom.xml');
my $atom = posts-to-atom self;
$atom-path.spurt: format-xml(~$atom);
# Create the resources folder and copy over our style sheets
my $res-dir = $out-dir.add('resources/');
mkdir $res-dir unless $res-dir.e;
$res-dir.add('colors.css').spurt: %?RESOURCES<colors.css>.slurp;
$res-dir.add('main.css').spurt: %?RESOURCES<main.css>.slurp;
$res-dir.add('code.css').spurt: %?RESOURCES<code.css>.slurp;
}
#| Get a list of posts sorted by date
method sorted-posts() {
%!posts.sort(*.value.posted-at).reverse
}
}
#| Read the database out of a directory
sub read-db(IO::Path:D $dir --> PostDB:D) is export {
my $posts-dir = $dir.add('posts/');
die "DB directory does not exist" unless $dir.e;
die "posts directory does not exist" unless $posts-dir.e;
# Read metadata
my $meta = BlogMeta.from-json: $dir.add('meta.json').slurp;
# Read posts
my %posts{Int} of PostTypes = %();
for dir $posts-dir -> $post {
my $id = $post.extension("").basename.Int;
# TODO: Dejankify this, maybe see if we can work with parsed, but
# unmarshalled json
given $post.slurp {
when /'"placeholder": true'/ {
%posts{$id} = PlaceholderPost.from-json: $_;
}
when /'"markdown": true'/ {
%posts{$id} = MarkdownPost.from-json: $_;
}
when /'"idris": true'/ {
%posts{$id} = IdrisPost.from-json: $_;
}
default {
die "Unsupported post type: $post";
}
}
}
# Build db structure
PostDB.new: meta => $meta, posts => %posts
}