File Coverage

blib/lib/Data/Section/Pluggable.pm
Criterion Covered Total %
statement 182 185 98.3
branch 54 62 87.1
condition 6 8 75.0
subroutine 24 24 100.0
pod 4 6 66.6
total 270 285 94.7


line stmt bran cond sub pod time code
1 14     14   1141417 use warnings;
  14         28  
  14         848  
2 14     14   307 use 5.020;
  14         60  
3 14     14   6712 use true;
  14         179897  
  14         115  
4 14     14   18732 use experimental qw( signatures );
  14         16499  
  14         89  
5 14     14   10978 use stable qw( postderef );
  14         6422  
  14         90  
6              
7             package Data::Section::Pluggable 0.08 {
8              
9             # ABSTRACT: Read structured data from __DATA__
10              
11              
12 14     14   9552 use Class::Tiny qw( package prefer_filesystem filename _formats _cache );
  14         41594  
  14         88  
13 14     14   14061 use Exporter qw( import );
  14         37  
  14         654  
14 14     14   8258 use Ref::Util qw( is_ref is_plain_hashref is_coderef is_plain_arrayref is_blessed_ref );
  14         39803  
  14         1763  
15 14     14   15990 use MIME::Base64 qw( decode_base64 encode_base64 );
  14         11505  
  14         1224  
16 14     14   13102 use Path::Tiny 0.130 ();
  14         237746  
  14         609  
17 14     14   166 use Carp ();
  14         30  
  14         20807  
18              
19             our @EXPORT_OK = qw( get_data_section );
20              
21 26     26 0 1758199 sub BUILDARGS ($class, @args) {
  26         118  
  26         54  
  26         46  
22 26 100       192 if(@args == 1) {
23 8 100       48 return $args[0] if is_plain_hashref $args[0];
24 7         65 return { package => $args[0] };
25             } else {
26 18         74 my %args = @args;
27 18         76 return \%args;
28             }
29             }
30              
31 26     26 0 607 sub BUILD ($self, $) {
  26         75  
  26         43  
32 26 100       967 unless(defined $self->package) {
33 17         242 my $package = caller 2;
34 17         373 $self->package($package);
35             }
36 26         201 foreach my $attr (qw( prefer_filesystem filename )) {
37 52 100       1210 if(defined $self->$attr) {
38 2 100 66     28 unless(is_blessed_ref($self->$attr) && $self->$attr->isa('Path::Tiny')) {
39 1         22 $self->$attr(Path::Tiny->new($self->$attr)->absolute);
40             }
41             }
42             }
43 26         712 $self->_formats({});
44             }
45              
46              
47 32     32 1 657922 sub get_data_section ($self=undef, $name=undef) {
  32         95  
  32         77  
  32         66  
48              
49             # handle being called as a function instead of
50             # a method.
51 32 100       121 unless(is_ref $self) {
52 4         19 $name = $self;
53 4         57 $self = __PACKAGE__->new(scalar caller);
54             }
55              
56 32         150 my $all = $self->_get_all_data_sections;
57 32 100       390 return undef unless $all;
58              
59 31 100       109 if (defined $name) {
60 17 100       53 if(exists $all->{$name}) {
61 16         66 return $self->_format($name, $all->{$name});
62             }
63 1         5 return undef;
64             } else {
65 14         59 return $self->_format_all($all);
66             }
67             }
68              
69 14     14   32 sub _format_all ($self, $all) {
  14         27  
  14         23  
  14         35  
70 14         24 my %new;
71 14         60 foreach my $key (keys %$all) {
72 28         101 $new{$key} = $self->_format($key, $all->{$key});
73             }
74 14         72 \%new;
75             }
76              
77 44     44   71 sub _format ($self, $name, $content) {
  44         63  
  44         76  
  44         88  
  44         62  
78 44         138 $content = $self->_decode($content->@*);
79 44 100       303 if($name =~ /\.(.*?)\z/ ) {
80 42         99 my $ext = $1;
81 42 100       873 if($self->_formats->{$ext}) {
82 19         452 $content = $_->($self, $content) for $self->_formats->{$ext}->@*;
83             }
84             }
85 44         4077 return $content;
86             }
87              
88 44     44   64 sub _decode ($self, $content, $encoding) {
  44         95  
  44         81  
  44         82  
  44         62  
89 44 100       153 return $content unless $encoding;
90 6 50       33 if($encoding ne 'base64') {
91 0         0 Carp::croak("unknown encoding: $encoding");
92             }
93 6         23 return decode_base64($content);
94             }
95              
96 33     33   51 sub _get_all_data_sections ($self) {
  33         55  
  33         51  
97 33 100       835 return $self->_cache if $self->_cache;
98              
99 23         157 my $fh;
100              
101 23 100       435 if($self->filename) {
102 1         15 $fh = $self->filename->openr_raw;
103             } else {
104 14     14   193 $fh = do { no strict 'refs'; \*{$self->package."::DATA"} };
  14         32  
  14         24899  
  22         152  
  22         39  
  22         446  
105             }
106              
107 23 100       379 return undef unless defined fileno $fh;
108              
109             # Question: does this handle corner case where perl
110             # file is just __DATA__ section? turns out, yes!
111             # added test t/data_section_pluggable__data_only.t
112 22         253 seek $fh, 0, 0;
113 22         47 my $content = do { local $/; <$fh> };
  22         130  
  22         942  
114 22         431 $content =~ s/^.*\n__DATA__\n/\n/s; # for win32
115 22         147 $content =~ s/\n__END__\n.*$/\n/s;
116              
117 22         355 my @data = split /^@@\s+(.+?)\s*\r?\n/m, $content;
118              
119             # extra at start whitespace, or __DATA_ for data only file
120 22         63 shift @data;
121              
122 22         94 my $all = {};
123 22         116 while (@data) {
124 42         169 my ($name_encoding, $content) = splice @data, 0, 2;
125 42         77 my ($name, $encoding);
126 42 100       228 if($name_encoding =~ /^(.*)\s+\((.*?)\)$/) {
127 4         24 $name = $1;
128 4         11 $encoding = $2;
129             } else {
130 38         66 $name = $name_encoding;
131             }
132 42 100 100     957 if($self->prefer_filesystem && -f (my $path = $self->prefer_filesystem->child($name))) {
133 2 100       178 $content = $encoding ? encode_base64($path->slurp_raw) : $path->slurp_utf8;
134             }
135 42         876 $all->{$name} = [ $content, $encoding ];
136             }
137              
138 22         515 return $self->_cache($all);
139             }
140              
141              
142 19     19 1 862 sub add_format ($self, $ext, $cb) {
  19         54  
  19         34  
  19         41  
  19         30  
143 19 50       55 Carp::croak("callback is not a code reference") unless is_coderef $cb;
144 19         494 push $self->_formats->{$ext}->@*, $cb;
145 19         267 return $self;
146             }
147              
148              
149 13     13 1 15656 sub add_plugin ($self, $name, %args) {
  13         28  
  13         31  
  13         30  
  13         24  
150              
151 13 50       95 Carp::croak("plugin name must match [a-z][a-z0-9_]+, got $name")
152             unless $name =~ /^[a-z][a-z0-9_]+\z/;
153              
154 13         99 my $class = join '::', 'Data', 'Section', 'Pluggable', 'Plugin', ucfirst($name =~ s/_(.)/uc($1)/egr);
  6         57  
155 13         88 my $pm = ($class =~ s!::!/!gr) . ".pm";
156              
157 13 100       61 require $pm unless $self->_valid_plugin($class);
158              
159 13         2103 my $plugin;
160 13 100       121 if($class->can("new")) {
161 6         32 $plugin = $class->new(%args);
162             } else {
163 7 50       26 if(%args) {
164 0         0 Carp::croak("extra arguments are not allowed for class plugins (hint create constructor)");
165             }
166 7         17 $plugin = $class;
167             }
168              
169 13 50       1382 Carp::croak("$class is not a valid Data::Section::Pluggable plugin")
170             unless $self->_valid_plugin($plugin);
171              
172 13 50       209 if($plugin->does('Data::Section::Pluggable::Role::ContentProcessorPlugin')) {
173 13         308 my @extensions = $plugin->extensions;
174 13 100       10606 @extensions = $extensions[0]->@* if is_plain_arrayref $extensions[0];
175 13 50       62 die "extensions method for $class returned no extensions" unless @extensions;
176              
177 16     16   138 my $cb = sub ($self, $content) {
  16         31  
  16         63  
  16         41  
178 16         107 return $plugin->process_content($self, $content);
179 13         74 };
180              
181 13         83 $self->add_format($_, $cb) for @extensions;
182              
183             };
184              
185 13         115 return $self;
186             }
187              
188 26     26   63 sub _valid_plugin ($self, $plugin) {
  26         50  
  26         47  
  26         41  
189 26 100       4457 $plugin->can('does') && $plugin->does('Data::Section::Pluggable::Role::ContentProcessorPlugin');
190             }
191              
192              
193 1     1 1 12 sub extract ($self, $dir=undef) {
  1         2  
  1         3  
  1         1  
194 1   50     6 $dir = Path::Tiny->new($dir // '.');
195              
196 1         50 my $all = $self->_get_all_data_sections;
197              
198 1         8 foreach my $key (keys %$all) {
199 3         5639 my $path = $dir->child($key);
200 3         121 $path->parent->mkdir;
201 3         663 my($content,$encoding) = $all->{$key}->@*;
202              
203 3 100       9 if(defined $encoding) {
204 1 50       5 if($encoding eq 'base64') {
205 1         10 $path->spew_raw(decode_base64($content));
206             } else {
207 0         0 Carp::croak("unknown encoding: $encoding");
208             }
209             } else {
210 2         8 $path->spew_utf8($content);
211             }
212             }
213             }
214              
215             }
216              
217             __END__