diff --git a/.gitignore b/.gitignore index 4190565..eabcb0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env/ .direnv/ **/.precomp/ +test-db/ diff --git a/blog b/blog index 45a2f74..7f6b45f 100755 --- a/blog +++ b/blog @@ -10,36 +10,28 @@ my %*SUB-MAIN-OPTS = :bundling, ; -my IO::Path:D $blog-dir = $*PROGRAM.parent; -#= The directory this script is located in +#| The directory this script is located in +my IO::Path:D $default-blog-dir = $*PROGRAM.parent; -#| Load the database from the provided file -sub load-db(IO::Path $file --> DB::PostDB:D) { - my $result = DB::PostDB.from-json: $file.slurp; - if $result ~~ DB::PostDB:D { - return $result; +#| Default database directory +my IO::Path:D $default-db-dir = + do if %*ENV { + $default-blog-dir.add('test-db/') } else { - die "Error parsing $file as databse: $result"; - } -} - -#| Write the databse to the provided file -sub write-db(IO::Path $file, DB::PostDB $db) { - my $output = $db.to-json; - $file.spurt: $output; -} + $default-blog-dir.add('db/') + }; #| Initalize the database multi MAIN( "db", "init", #| The path of the database file - IO::Path(Str) :$db-file = $blog-dir.add("db.json"), + IO::Path(Str) :$db-dir = $default-db-dir, #| Overwrite an already existing database file Bool :$force ) { - die "Database file already exists, use --force to overwrite: {$db-file.Str}" - if $db-file.e && !$force; + die "Database folder already exists, use --force to overwrite: {$db-dir.Str}" + if $db-dir.e && !$force; print "Blog Title: "; my $title = get; @@ -50,11 +42,7 @@ multi MAIN( my $db = DB::PostDB.init: $meta; - if $force { - $db-file.spurt: $db.to-json, :create-only; - } else { - $db-file.spurt: $db.to-json; - } + $db.write: $db-dir; } #| Ensure that the database loads, erroring otherwise @@ -62,9 +50,9 @@ multi MAIN( "db", "verify", #| The path of the database file - IO::Path(Str) :$db = $blog-dir.add("db.json"), + IO::Path(Str) :$db-dir = $default-db-dir, ) { - load-db $db; + read-db $db-dir; say "Database OK"; } @@ -76,13 +64,13 @@ multi MAIN( #| The path to the markdown file IO::Path(Str) $source, #| The path of the database file - IO::Path(Str) :$db-file = $blog-dir.add("db.json"), + IO::Path(Str) :$db-dir = $default-db-dir, #| The date/time the post should be recorded as posted at DateTime(Str) :$posted-at = DateTime.now, #| Should the post be hidden from the archive? Bool :$hidden = False, ) { - my $db = load-db $db-file; + my $db = read-db $db-dir; my $id = $db.insert-post: MarkdownPost.new( @@ -90,7 +78,7 @@ multi MAIN( posted-at => $posted-at, hidden => $hidden, ); - write-db $db-file, $db; + $db.write: $db-dir; say 'Post inserted with id ', $id; say 'Post has slugs: ', $db.posts{$id}.all-slugs; } diff --git a/lib/DB.rakumod b/lib/DB.rakumod index ef3ca23..e1d4365 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -12,29 +12,18 @@ use DB::MarkdownPost; use DB::IdrisPost; use DB::PlaceholderPost; -class Posts is json( - :dictionary( - :keyof(Int:D), - MarkdownPost:D, - IdrisPost:D, - PlaceholderPost:D, - )) {} - subset PostTypes where MarkdownPost:D | IdrisPost:D | PlaceholderPost:D; #| The top level posts database -class PostDB is json(:pretty) { - #| The metadat for the blog +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 is Posts; + has %.posts{Int} of PostTypes = %(); #| The post id to use for placeholder posts has Int $.placeholder-id = 0; - method TWEAK() { - %!posts := Posts.new unless %!posts; - } - #| Get the next unused post ID method next-post-id(--> Int) { if %!posts.elems > 0 { @@ -53,11 +42,57 @@ class PostDB is json(:pretty) { #| Initialize a new database method init(BlogMeta:D $meta --> PostDB:D) { - my %posts is Posts = Posts.new; + 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; + } + } +} + +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 } diff --git a/lib/DB/BlogMeta.rakumod b/lib/DB/BlogMeta.rakumod index 032e5da..f2a0382 100644 --- a/lib/DB/BlogMeta.rakumod +++ b/lib/DB/BlogMeta.rakumod @@ -3,8 +3,13 @@ use v6.e.PREVIEW; use JSON::Class:auth; # Top level metadata for the blog -unit class BlogMeta is json(:pretty); #| The title of the blog +unit class BlogMeta is json(:pretty); + +#| The title of the blog +has Str:D $.title is required is rw; -has Str:D $.title is required; #| The tagline of the blog -has Str:D $.tagline is required; +has Str:D $.tagline is required is rw; + +#| The id of the placeholder post +has Int:D $.placeholder-id is rw = 0; diff --git a/lib/DB/IdrisPost.rakumod b/lib/DB/IdrisPost.rakumod index e195dac..850ff83 100644 --- a/lib/DB/IdrisPost.rakumod +++ b/lib/DB/IdrisPost.rakumod @@ -7,7 +7,7 @@ use DB::Post; #| A literate, markdown, idris post -unit class IdrisPost does Post is json; +unit class IdrisPost does Post is json(:pretty); #| Marker for disambiguation between post types in json representation, the #| cheaty way diff --git a/lib/DB/MarkdownPost.rakumod b/lib/DB/MarkdownPost.rakumod index 03a3559..372fa31 100644 --- a/lib/DB/MarkdownPost.rakumod +++ b/lib/DB/MarkdownPost.rakumod @@ -6,7 +6,7 @@ use JSON::Class:auth; use DB::Post; #| A plain markdown post -unit class MarkdownPost does Post is json; +unit class MarkdownPost does Post is json(:pretty); #| Marker for disambiguation between post types in json representation, the #| cheaty way diff --git a/lib/DB/PlaceholderPost.rakumod b/lib/DB/PlaceholderPost.rakumod index 74d1ac8..e30d38b 100644 --- a/lib/DB/PlaceholderPost.rakumod +++ b/lib/DB/PlaceholderPost.rakumod @@ -6,7 +6,7 @@ use JSON::Class:auth; use DB::Post; #| An empty placeholder post -unit class PlaceholderPost does Post is json; +unit class PlaceholderPost does Post is json(:pretty); #| Marker for disambiguation between post types in json representation, the #| cheaty way