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
7 changes: 5 additions & 2 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ Revision history for {{$dist->name}}

{{$NEXT}}
- Don't write file if __DATA__ hasn't changed (gh#2)
- Add ->unchanged method (gh#2)
- Add integration tests and documentation re: Data::Section::Pluggable (gh#3)
- Added ->unchanged method (gh#2)
- Added integration tests and documentation re: Data::Section::Pluggable (gh#3)
- Added Data::Section::Pluggable::FormatContentPlugin (gh#4)
- Added ->add_format method (gh#2)
- Added ->add_plugin method (gh#2)

0.01 2024-12-03 17:15:21 -0700
- initial version
Expand Down
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Starting with version 0.02, this method will not write to the file if the conten
\[version 0.02\]

```perl
my $bool = $self->unchanged;
my $bool = $writer->unchanged;
```

Returns:
Expand All @@ -103,6 +103,46 @@ Returns:

If the last call to </update\_file> did not modify the file.

## add\_format

```perl
$writer->add_format( $ext, sub ($writer, $content) { return ... } );
```

Adds a content formatter to the given filename extension. The extension should be a filename extension without the `.`, for example `txt` or `json`.

The callback takes the [Data::Section::Writable](https://metacpan.org/pod/Data::Section::Writable) instance as its first argument and the content to be processed as the second.
This callback should return the format content as a scalar.

You can chain multiple content formatters to the same filename extension, and they will be called in the order that they were added.

## add\_plugin

```
$writer->add_plugin( $name, %args );
```

Applies the plugin with `$name`. If the plugin supports instance mode (that is: it has a constructor named new), then %args will be passed to the
constructor. For included plugins see ["CORE PLUGINS"](#core-plugins). To write your own see ["PLUGIN ROLES"](#plugin-roles).

# CORE PLUGINS

This module will work with some core [Data::Section::Pluggable](https://metacpan.org/pod/Data::Section::Pluggable) plugins listed here.

## json

Automatically encode json into Perl data structures.

See [Data::Section::Pluggable::Plugin::Json](https://metacpan.org/pod/Data::Section::Pluggable::Plugin::Json).

# PLUGIN ROLES

## FormatContentPlugin

Used for adding content formatting for specific formats. This
is essentially a way to wrap the [add\_format method](#add_format)
as a module. See [Data::Section::Pluggable::Role::FormatContentPlugin](https://metacpan.org/pod/Data::Section::Pluggable::Role::FormatContentPlugin).

# CAVEATS

Added text files will get an added trailing new line if they do not already have
Expand Down
3 changes: 2 additions & 1 deletion author.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ pod_spelling_system:
# (regardless of what spell check thinks)
# or stuff that I like to spell incorrectly
# intentionally
stopwords: []
stopwords:
- FormatContentPlugin

pod_coverage:
skip: 0
Expand Down
14 changes: 14 additions & 0 deletions lib/Data/Section/Pluggable/Role/FormatContentPlugin.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use warnings;
use 5.020;
use true;
use experimental qw( signatures );

package Data::Section::Pluggable::Role::FormatContentPlugin {

# ABSTRACT: Plugin role for Data::Section::Writer

use Role::Tiny;
requires 'extensions';
requires 'format_content';

}
108 changes: 102 additions & 6 deletions lib/Data/Section/Writer.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use warnings;
use 5.020;
use experimental qw( signatures );
use stable qw( postderef );
use true;

package Data::Section::Writer {

Expand Down Expand Up @@ -38,8 +39,8 @@ The name of the Perl source file. If not provided then the source for the calle

use Path::Tiny ();
use Carp ();
use Class::Tiny qw( perl_filename _files _same );
use Ref::Util qw( is_blessed_ref );
use Class::Tiny qw( perl_filename _files _same _formats );
use Ref::Util qw( is_coderef is_blessed_ref is_plain_arrayref );
use MIME::Base64 qw(encode_base64);

sub BUILD ($self, $) {
Expand All @@ -56,6 +57,7 @@ The name of the Perl source file. If not provided then the source for the calle
}

$self->_files({});
$self->_formats({});

}

Expand All @@ -81,10 +83,18 @@ only supported by L<Mojo::Loader> at the moment.
my $text = "@@ $filename";
$text .= " (" . $data->[1] . ")" if defined $data->[1];
$text .= "\n";

my $content = $data->[0];

if($filename =~ /\.(.*?)\z/ && ($self->_formats->{$1} // [])->@*) {
my $ext = $1;
$content = $_->($self, $content) for $self->_formats->{$ext}->@*;
}

if(defined $data->[1] && $data->[1] eq 'base64') {
$text .= encode_base64($data->[0]);
$text .= encode_base64($data->[0]);
} else {
$text .= $data->[0];
$text .= $content;
}
chomp $text;
return $text;
Expand Down Expand Up @@ -161,7 +171,7 @@ Starting with version 0.02, this method will not write to the file if the conten

[version 0.02]

my $bool = $self->unchanged;
my $bool = $writer->unchanged;

Returns:

Expand All @@ -187,9 +197,95 @@ If the last call to </update_file> did not modify the file.
return $self->_same;
}

=head2 add_format

$writer->add_format( $ext, sub ($writer, $content) { return ... } );

Adds a content formatter to the given filename extension. The extension should be a filename extension without the C<.>, for example C<txt> or C<json>.

The callback takes the L<Data::Section::Writable> instance as its first argument and the content to be processed as the second.
This callback should return the format content as a scalar.

You can chain multiple content formatters to the same filename extension, and they will be called in the order that they were added.

=cut

sub add_format ($self, $ext, $cb) {
Carp::croak("callback is not a code reference") unless is_coderef $cb;
push $self->_formats->{$ext}->@*, $cb;
return $self;
}

=head2 add_plugin

$writer->add_plugin( $name, %args );

Applies the plugin with C<$name>. If the plugin supports instance mode (that is: it has a constructor named new), then %args will be passed to the
constructor. For included plugins see L</CORE PLUGINS>. To write your own see L</PLUGIN ROLES>.

=cut

sub add_plugin ($self, $name, %args) {
Carp::croak("plugin name must match [a-z][a-z0-9_]+, got $name")
unless $name =~ /^[a-z][a-z0-9_]+\z/;

my $class = join '::', 'Data', 'Section', 'Pluggable', 'Plugin', ucfirst($name =~ s/_(.)/uc($1)/egr);
my $pm = ($class =~ s!::!/!gr) . ".pm";

require $pm unless $self->_valid_plugin($class);

my $plugin;
if($class->can("new")) {
$plugin = $class->new(%args);
} else {
if(%args) {
Carp::croak("extra arguments are not allowed for class plugins (hint create constructor)");
}
$plugin = $class;
}

Carp::croak("$class is not a valid Data::Section::Pluggable plugin")
unless $self->_valid_plugin($plugin);

if($plugin->does('Data::Section::Pluggable::Role::FormatContentPlugin')) {

my @extensions = $plugin->extensions;
@extensions = $extensions[0]->@* if is_plain_arrayref $extensions[0];

die "extensions method for $class returned no extensions" unless @extensions;

my $cb = sub ($self, $content) {
return $plugin->format_content($self, $content);
};

$self->add_format($_, $cb) for @extensions;
}

return $self;
}

sub _valid_plugin ($self, $plugin) {
$plugin->can('does') && $plugin->does('Data::Section::Pluggable::Role::FormatContentPlugin');
}
}

1;
=head1 CORE PLUGINS

This module will work with some core L<Data::Section::Pluggable> plugins listed here.

=head2 json

Automatically encode json into Perl data structures.

See L<Data::Section::Pluggable::Plugin::Json>.

=head1 PLUGIN ROLES

=head2 FormatContentPlugin

Used for adding content formatting for specific formats. This
is essentially a way to wrap the L<add_format method|/add_format>
as a module. See L<Data::Section::Pluggable::Role::FormatContentPlugin>.

=head1 CAVEATS

Expand Down
3 changes: 3 additions & 0 deletions t/00_diag.t
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ $modules{$_} = $_ for qw(
Mojolicious
Path::Tiny
Ref::Util
Role::Tiny
Role::Tiny::With
Test2::API
Test2::Require::Module
Test2::V0
Test::Differences
stable
true
);


Expand Down
48 changes: 48 additions & 0 deletions t/data_section_pluggable_role_formatcontentplugin.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use Test2::V0 -no_srand => 1;
use experimental qw( signatures );
use Data::Section::Writer;

plan 3;

package Data::Section::Pluggable::Plugin::MyPlugin1 {
use Role::Tiny::With;
with 'Data::Section::Pluggable::Role::FormatContentPlugin';

sub extensions ($class) {
package main;
is(
[$class],
['Data::Section::Pluggable::Plugin::MyPlugin1'],
'arguments to ->extensions'
);
return ('txt')
}

sub format_content ($class, $writer, $content) {
package main;
is(
[ $class, $writer, $content ],
[ 'Data::Section::Pluggable::Plugin::MyPlugin1', object { prop isa => 'Data::Section::Writer' }, "hello world" ],
'arguments to ->process_content',
);
return "[$content]"
}

}

is(
Data::Section::Writer->new->add_plugin('my_plugin1'),
object {
prop isa => 'Data::Section::Writer';
call [add_file => "hello.txt", "hello world"] => object {};
call [add_file => "hello.t2", "hello world"] => object {};
call render_section => "__DATA__\n" .
"\@\@ hello.t2\n" .
"hello world\n" .
"\@\@ hello.txt\n" .
"[hello world]\n";
},
'simple',
);

done_testing;
Loading