From ce364c01d809bb9b0134b1432d550e9966a3fd89 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Tue, 4 Feb 2025 16:51:37 -0500 Subject: [PATCH] Description generation --- lib/Config.rakumod | 31 ++++++++++++----- lib/DB.rakumod | 4 +-- lib/DB/IdrisPost.rakumod | 13 +++++++ lib/DB/MarkdownPost.rakumod | 13 +++++++ lib/DB/Post.rakumod | 5 +++ lib/Pandoc.rakumod | 69 ++++++++++++++++++++++++++----------- 6 files changed, 102 insertions(+), 33 deletions(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index b836b95..af798d0 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -2,29 +2,42 @@ use v6.e.PREVIEW; use HTML::Functional; use DB::BlogMeta; +use DB::Post; unit class Config; -# TODO: Support GFM admonitions -method generate-post(Str:D $title, Str:D $content, BlogMeta:D $meta) { - my $head = head [ +method generate-head(Str:D $title, BlogMeta:D $meta, $description?) { + head [ meta :charset; meta :name, :content; + meta :author :content; title "{$meta.title} — $title"; - # TODO: Add style sheets - # Use Iosevka Alie as the monospace font - link :rel, - :href; - # Use open sans as the content font + # Add description, if one exists + do if $description ~~ Str:D { + meta :description :content($description) + } else { + [] + } + # Preconnect to all our resource sources + link :rel :href; link :rel :href; link :rel :href :crossorigin; + # Load fonts, Iosevka Alie for code, and Open Sans for content + link :rel, + :href; link :rel, :href; + # Inline our style sheets style %?RESOURCES.slurp; style %?RESOURCES.slurp; - # TODO: Add description # TODO: Add header links ]; +} + +# TODO: Support GFM admonitions +method generate-post(Post:D $post, BlogMeta:D $meta) { + my $content = $post.render-html; + my $head = self.generate-head($post.title, $meta, $post.description); my $body = body [ div :class, [ diff --git a/lib/DB.rakumod b/lib/DB.rakumod index 00b70fa..fd95f6f 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -78,9 +78,7 @@ class PostDB { 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.title, $post.render-html, $!meta; + my $html = $config.generate-post: $post, $!meta; my $id-path = $by-id.add: "$id.html"; $id-path.spurt: $html; for $post.all-slugs -> $slug { diff --git a/lib/DB/IdrisPost.rakumod b/lib/DB/IdrisPost.rakumod index 27a44a2..1e229ac 100644 --- a/lib/DB/IdrisPost.rakumod +++ b/lib/DB/IdrisPost.rakumod @@ -14,6 +14,9 @@ unit class IdrisPost does Post is json(:pretty); #| cheaty way has Bool:D $.idris is json = True; +#| Override the generated description for this post +has Str $.summary is json; + #| Location of the ipkg for the package containing the post has IO::Path:D $.ipkg is required @@ -48,6 +51,16 @@ method render-html(--> Str:D) { }; } +# Return our summary, if we have one, otherwise extract the first paragraph of +# the markdown document +method description(--> Str) { + if $!summary { + $!summary + } else { + markdown-first-paragraph $!source + } +} + # Run a pack command, erroring on failure sub pack(*@args) { my $pack = run 'pack', @args, :out, :err; diff --git a/lib/DB/MarkdownPost.rakumod b/lib/DB/MarkdownPost.rakumod index f901ce3..0f189d5 100644 --- a/lib/DB/MarkdownPost.rakumod +++ b/lib/DB/MarkdownPost.rakumod @@ -11,6 +11,9 @@ unit class MarkdownPost does Post is json(:pretty); #| cheaty way has Bool:D $.markdown = True; +#| Override the generated description for this post +has Str $.summary; + method title(--> Str:D) { markdown-title $!source } @@ -19,3 +22,13 @@ method title(--> Str:D) { method render-html(--> Str:D) { markdown-to-html $!source } + +# Return our summary, if we have one, otherwise extract the first paragraph of +# the markdown document +method description(--> Str) { + if $!summary { + $!summary + } else { + markdown-first-paragraph $!source + } +} diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod index f27c2b6..0c43fdb 100644 --- a/lib/DB/Post.rakumod +++ b/lib/DB/Post.rakumod @@ -53,3 +53,8 @@ method all-slugs(--> Array[Str:D]) { #| Render this post to an html body method render-html(--> Str:D) {...} + +#| Get the description for this post, returning nil if there is none +method description(--> Str) { + Nil +} diff --git a/lib/Pandoc.rakumod b/lib/Pandoc.rakumod index fb80e4e..74048f0 100644 --- a/lib/Pandoc.rakumod +++ b/lib/Pandoc.rakumod @@ -3,13 +3,10 @@ unit module Pandoc; use JSON::Fast; -#| Extract the title from a markdown document -#| -#| The title is the only top level header, will throw an error if there are -#| multiple top level headers or are none -sub markdown-title(IO::Path:D $file --> Str:D) is export { +#| Run pandoc with the given arguments, dieing on failure +sub pandoc(*@args --> Str:D) { # Call into pandoc - my $pandoc = run , $file, :out, :err; + my $pandoc = run 'pandoc', @args, :out, :err; # Collect the output my $output = $pandoc.out.slurp: :close; @@ -17,6 +14,17 @@ sub markdown-title(IO::Path:D $file --> Str:D) is export { die "Pandoc exited with {$pandoc.exitcode}\nout: $output\nerr: $stderr" unless $pandoc; + $output +} + +#| Extract the title from a markdown document +#| +#| The title is the only top level header, will throw an error if there are +#| multiple top level headers or are none +sub markdown-title(IO::Path:D $file --> Str:D) is export { + # Collect the output + my $output = pandoc <-f gfm -t JSON>, $file; + # Parse out output from pandoc, we are making an executive decision to trust # pandoc here, so we won't do any error handling for pandoc's output my %parsed = from-json $output; @@ -27,12 +35,8 @@ sub markdown-title(IO::Path:D $file --> Str:D) is export { $v ~~ Associative && $v ~~ "Header" } my @headers = %parsed.grep(&is-header).grep(*[0] == 1); - if @headers.elems > 1 { - die "More than one top level header in $file"; - }; - if @headers.elems == 0 { - die "No top level headers in $file"; - }; + die "More than one top level header in $file" if @headers.elems > 1; + die "No top level headers in $file" if @headers.elems == 0; # Extract the header and process it into a string my @header = @headers[0][2].flat; @@ -55,15 +59,38 @@ sub markdown-title(IO::Path:D $file --> Str:D) is export { return $title; } +#| Use pandoc to extract the first paragraph of a markdown document +sub markdown-first-paragraph(IO::Path:D $file --> Str:D) is export { + my $output = pandoc <-f gfm -t JSON>, $file; + my %parsed = from-json $output; + # Extract a list of paragraphs from the pandoc output + my sub is-para($v) { + $v ~~ Associative && $v ~~ 'Para' + } + my @paras = %parsed.grep(&is-para); + die "No paragraphs in markdown" if @paras.elems == 0; + my @para = @paras[0][0].flat; + # Proces it into a string + my $para = ""; + for @para -> $component { + next unless $component ~~ Associative; + given $component { + when "Str" { + $para ~= $component; + } + when "Space" { + $para ~= " "; + } + default { + die "Invalid component type: $_"; + } + } + } + + $para +} + #| Use pandoc to render a markdown document to html sub markdown-to-html(IO::Path:D $file --> Str:D) is export { - # Call into pandoc - my $pandoc = run , $file, :out, :err; - # Collect the output - my $output = $pandoc.out.slurp: :close; - my $stderr = $pandoc.err.slurp: :close; - die "Pandoc exited with {$pandoc.exitcode}\nout: $output\nerr: $stderr" - unless $pandoc; - - $output + pandoc <-f gfm>, $file }