Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ All publication writes are atomic: `_publish_template_to_file`/`_atomically_writ

- **`Plerd::Post`** (`Post.pm`) — one post per Markdown source file. The real work happens in `_process_source_file`, fired by a Moose **trigger** the moment `source_file` is set (not lazily). That method:
- parses the leading `key: value` metadata block (terminated by a blank line) into `attributes`, then the Markdown body;
- runs the body and title through `Text::MultiMarkdown::markdown` then `Plerd::SmartyPants`;
- runs the body and title through `Markdown::Perl` (a shared converter in GitHub mode, so fenced code blocks, tables, and the rest of GFM work) then `Plerd::SmartyPants`. The converter drops `"` from its HTML-escaped characters so SmartyPants can curl literal double quotes into typographic ones; quotes inside code are still escaped via `html_escaped_code_characters`;
- **mutates the source file in place** — if `time`, `published_filename`, or `guid` are missing, it computes them and rewrites the file with the full metadata block. This write-back is core behavior, not a side effect to "fix." Date logic: explicit W3C `time:` wins; else a `YYYY-MM-DD` filename prefix sets midnight of that date (or now, if the date is today); else now.
- resolves tags (comma-separated `tags:` header) into shared `Plerd::Tag` objects via `$plerd->tag_named`.
- `publish` renders `post.tt`. `send_webmentions` walks the post's links via `Web::Mention`.
Expand Down
6 changes: 5 additions & 1 deletion cpanfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Markdown::Perl (our Markdown engine) requires Perl 5.26 or later.
requires 'perl', '5.026';

requires 'Moose';
requires 'Template';
requires 'Path::Class';
Expand All @@ -7,7 +10,7 @@ requires 'JSON';
requires 'DateTime' => '1.54';
requires 'DateTime::Format::W3CDTF';
requires 'URI';
requires 'Text::MultiMarkdown';
requires 'Markdown::Perl';
requires 'YAML';
requires 'FindBin';
requires 'File::ChangeNotify';
Expand All @@ -21,3 +24,4 @@ requires 'Readonly';
requires 'Web::Mention' => '0.703';
requires 'MooseX::Types::URI';
requires 'Test::Warn';
requires 'Capture::Tiny'; # Used by t/init.t.
18 changes: 13 additions & 5 deletions lib/Plerd/Post.pm
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package Plerd::Post;
use Moose;
use DateTime;
use DateTime::Format::W3CDTF;
use Text::MultiMarkdown qw( markdown );
use Markdown::Perl;
use URI;
use HTML::Strip;
use Data::GUID;
Expand All @@ -18,6 +18,13 @@ use Web::Mention;
use Readonly;
Readonly my $WPM => 200; # The words-per-minute reading speed to assume

my $markdown = Markdown::Perl->new(
mode => 'github',
# Drop '"' so literal quotes survive for Plerd::SmartyPants to curl.
html_escaped_characters => '&<>',
);
sub markdown { $markdown->convert( $_[0] ) }

has 'plerd' => (
is => 'ro',
required => 1,
Expand Down Expand Up @@ -444,10 +451,11 @@ sub _process_source_file {
}
$self->body( $body );

foreach ( qw( title body ) ) {
if ( defined( $self->$_ ) ) {
$self->$_( Plerd::SmartyPants::process( markdown( $self->$_ ) ) );
}
foreach my $field ( qw( title body ) ) {
next unless defined $self->$field;
$self->$field(
Plerd::SmartyPants::process( markdown( $self->$field ) )
);
}

# Strip unnecessary <p> tags that the markdown processor just added to the title.
Expand Down
75 changes: 75 additions & 0 deletions t/code_blocks.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use warnings;
use strict;
use Test::More;
use Path::Class::Dir;
use Path::Class::File;
use URI;

use FindBin;
use lib "$FindBin::Bin/../lib";

use_ok( 'Plerd' );
use Plerd::Init;

# Regression test for GitHub issue #57: GitHub-style fenced code blocks.
# Text::MultiMarkdown does not understand ``` or ~~~ fences -- it collapsed a
# backtick-fenced block into a single inline <code> span (so every line
# wrapped together) and ignored ~~~ entirely. Plerd now pre-processes fences
# into <pre><code> blocks before handing the body to Markdown.

my $blog_dir = Path::Class::Dir->new( "$FindBin::Bin/code_blocks_blog" );
$blog_dir->rmtree;
Plerd::Init::initialize( $blog_dir->stringify, 0 );

my $source = <<'END';
title: Code

Backtick fence with a language:

```perl
my $x = 1;
if ( $x < 2 ) { say "ok"; }
```

Tilde fence:

~~~
plain text
second line
~~~

Inline `code` is untouched.
END

Path::Class::File->new( $blog_dir, 'source', '2022-07-31-code.md' )->spew(
iomode => '>:encoding(utf8)', $source,
);

my $plerd = Plerd->new(
path => $blog_dir->stringify,
title => 'Test Blog',
author_name => 'Nobody',
author_email => 'nobody@example.com',
base_uri => URI->new( 'http://blog.example.com/' ),
);

$plerd->publish_all;
my ( $post ) = @{ $plerd->posts };
my $body = $post->body;

like( $body, qr{<pre><code class="language-perl">},
'Backtick fence becomes a <pre><code> block with a language class.' );
like( $body, qr{my \$x = 1;\nif},
'Code lines are preserved on separate lines, not wrapped together.' );
like( $body, qr{\$x &lt; 2},
'Code special characters are HTML-escaped.' );
like( $body, qr{<pre><code>\s*plain text\nsecond line},
'Tilde fence also becomes a <pre><code> block.' );
like( $body, qr{<code>code</code>},
'Inline code spans still work.' );
unlike( $body, qr{<p><code>perl},
'Fence is not collapsed into a single inline code span.' );

$blog_dir->rmtree;

done_testing();