diff --git a/bin/plerdwatcher b/bin/plerdwatcher index 1c5c653..4bebb5c 100755 --- a/bin/plerdwatcher +++ b/bin/plerdwatcher @@ -75,10 +75,17 @@ my $plerd; my $watcher; try { $plerd = Plerd->new( $config_ref ); + my $filter; + + my $trigger = defined (keys %{$plerd->post_triggers}) + ? sprintf '\.(md|markdown|%s)$', join('|', keys %{$plerd->post_triggers}) + : '\.(md|markdown)$'; + + $filter = qr/$trigger/; $watcher = File::ChangeNotify->instantiate_watcher ( directories => [ $plerd->source_directory . '' ], - filter => qr/\.(md|markdown)$/, + filter => $filter, ); } catch { diff --git a/cpanfile b/cpanfile index 2eac0c2..985b888 100644 --- a/cpanfile +++ b/cpanfile @@ -15,6 +15,7 @@ requires 'Try::Tiny'; requires 'HTML::Strip'; requires 'HTML::SocialMeta' => '0.72'; requires 'List::Util' => '1.45'; +requires 'Module::Load'; requires 'Readonly'; -requires 'Web::Mention' => '0.6'; +requires 'Web::Mention' => '0.6'; requires 'Mojolicious::Lite'; diff --git a/lib/Plerd.pm b/lib/Plerd.pm index 54d3e2f..a9d0e73 100644 --- a/lib/Plerd.pm +++ b/lib/Plerd.pm @@ -12,6 +12,8 @@ use URI; use Carp; use Try::Tiny; +use Module::Load; + use Plerd::Post; use Plerd::WebmentionQueue; @@ -257,6 +259,23 @@ has 'tags_map' => ( clearer => 'clear_tags_map', ); +has 'extensions' => ( + is => 'ro', + isa => 'Maybe[ArrayRef[Str]]' +); + +has 'extension_preferences' => ( + is => 'ro', + isa => 'Maybe[HashRef]', +); + +has 'post_triggers' => ( + is => 'ro', + isa => 'Maybe[HashRef[Str]]', + lazy_build => 1, +); + + sub BUILD { my $self = shift; @@ -273,6 +292,16 @@ sub BUILD { } } + foreach my $extension (@{$self->extensions // []}) { + try { + load $extension; + } + catch { + my $error = shift || "Unknown error"; + die "Can't load extension: '$extension': $error\n"; + }; + } + return $self; } @@ -609,15 +638,24 @@ sub _build_recent_posts { sub _build_posts { my $self = shift; + my @posts; + my $triggers = $self->post_triggers; + + foreach my $file (sort { $a->basename cmp $b->basename } $self->source_directory->children) { + if ($file =~ m/\.(?:markdown|md)$/) { + push @posts, Plerd::Post->new( plerd => $self, source_file => $file ) + } else { + foreach my $trigger (keys %{$triggers}) { + if ($file =~ m/\.$trigger$/i) { + push @posts, $$triggers{$trigger}->new(plerd => $self, source_file => $file); + last; + } + } + } + } - my @posts = sort { $b->date <=> $a->date } - map { Plerd::Post->new( plerd => $self, source_file => $_ ) } - sort { $a->basename cmp $b->basename } - grep { /\.markdown$|\.md$/ } - $self->source_directory->children - ; + return [sort { $b->date <=> $a->date } @posts]; - return \@posts; } sub _build_index_of_post_with_guid { @@ -664,6 +702,19 @@ sub _throw_template_exception { . "template file $template_file: $error\n"; } +sub _build_post_triggers { + my $self = shift; + my %triggers; + + foreach my $classref (@{$self->extensions // []}){ + if ($classref->can('file_type')) { + $triggers{ $classref->file_type } = $classref; + } + } + + return \%triggers; +} + sub generates_post_guids { carp "generates_post_guids() is deprecated. (Also, it doesn't do anything " . "anyway.)"; @@ -954,6 +1005,22 @@ tag index HTML files. This is a L object that points to the tag index. It is particularly helpful when creating navigation. +=item extensions + +An arrayref of strings, representing the plugins to load when a new Plerd +instance is created. + +=item extension_preferences + +A hashref of config options for extensions. It is up to each individual extension +to decide how to act upon the contents therein. + +=item post_triggers + +A hashref that maps extensions to file types. The key is used as a regex to deduce what +source file types the particular Post extension using to render pages. +The value is a reference to that particular extension. + =back =head1 OBJECT METHODS diff --git a/t/basic.t b/t/basic.t index 4b283f1..a7a29ab 100644 --- a/t/basic.t +++ b/t/basic.t @@ -8,6 +8,7 @@ use DateTime; use FindBin; use lib "$FindBin::Bin/../lib"; +use lib "$FindBin::Bin/lib"; use_ok( 'Plerd' ); @@ -54,9 +55,10 @@ unlink "$FindBin::Bin/source/no-title.md"; $plerd->publish_all; -# The "+5" below accounts for the generated recent, archive, and RSS files, +# The "+4" below accounts for the generated recent, archive, and RSS files, # a index.html symlink, and a tags directory. -my $expected_docroot_count = scalar( $source_dir->children( no_hidden => 1 ) ) + 5; +my $expected_docroot_count = scalar( $source_dir->children( no_hidden => 1 ) ) + 4; + is( scalar( $docroot_dir->children ), $expected_docroot_count, "Correct number of files generated in docroot." @@ -331,6 +333,28 @@ like( $post, 'Metatags: Defined default alt-text', ); +} +{ +### Test extension-support +my $extension_plerd = Plerd->new( + path => $FindBin::Bin, + title => 'Test Blog', + author_name => 'Nobody', + author_email => 'nobody@example.com', + extensions => ['TestExtension'], + base_uri => URI->new ( 'http://blog.example.com/' ), + image => URI->new ( 'http://blog.example.com/logo.png' ), + +); + +$extension_plerd->publish_all; + +my $post = Path::Class::File->new( $docroot_dir, "$ymd-a-very-special-source-file.html" )->slurp; +like( $post, + qr{.sdrawkcab dm. si hcihw md.}, + 'Extensions: Plerd can publish with extensions', +); + } done_testing(); diff --git a/t/lib/TestExtension.pm b/t/lib/TestExtension.pm new file mode 100644 index 0000000..006da3f --- /dev/null +++ b/t/lib/TestExtension.pm @@ -0,0 +1,68 @@ +package TestExtension; +use Plerd::Post; +use Moose; +use strict; + +use 5.010; + +use warnings FATAL => 'all'; + +extends "Plerd::Post"; + +sub file_type { + 'dm'; +} + +around 'body' => sub { + my $orig = shift; + my $self = shift; + if (@_){ + # "Setter" + my $body = shift; + + # first time body is called, reverse it! + $body = reverse $body unless $self->$orig; + + $self->$orig($body); + } else { + # "Getter" + $self->$orig(); + } +}; + +1; + + +=head1 NAME + +TestExtension - A L extension to showcase the extension capabilities of L. + +=head1 DESCRIPTION + +This extension doesn't do anything truly useful. It's sole purpose is showcasing the extension capabilities. + +What it does in fact do, though, is reversing the body of the document (the first time it's set). + +It's rather useless. + +=head1 OBJECT ATTRIBUTES + +=head2 Read-only attributes, set during construction + +=over + +=item file_type + +A regex-compatible string which indicate what file types to associate with the Extension. +This is set to "dm" (which happens to be "md" spelled backwards) + +=item body + +The original L body, except that the first time this attribute is set, it gets reversed. + +=back + +=head2 Other attributes + +All other attributes are inherited from L Please consult that documentation for their docs. + diff --git a/t/source_model/extension.dm b/t/source_model/extension.dm new file mode 100644 index 0000000..5069039 --- /dev/null +++ b/t/source_model/extension.dm @@ -0,0 +1,4 @@ +title: A very special source file + + +It's only special because it's file type is .dm which is .md backwards. This is to see if it gets processed by an extension.