File Coverage

blib/lib/Asset/Pack.pm
Criterion Covered Total %
statement 114 122 93.4
branch 24 34 70.5
condition 5 8 62.5
subroutine 21 22 95.4
pod 3 3 100.0
total 167 189 88.3


line stmt bran cond sub pod time code
1 12     12   340401 use 5.006; # our
  12         33  
2 12     12   58 use strict;
  12         16  
  12         313  
3 12     12   67 use warnings;
  12         14  
  12         657  
4              
5             package Asset::Pack;
6              
7 12     12   5949 use Path::Tiny 0.069 qw( path ); # path()->visit without broken ref returns
  12         93568  
  12         767  
8 12     12   7761 use Try::Tiny qw( try catch );
  12         25252  
  12         920  
9              
10             our $VERSION = '0.000008';
11              
12             # ABSTRACT: Easily pack assets into Perl Modules that can be fat-packed
13              
14             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
15              
16 12     12   3635 use parent qw(Exporter);
  12         1986  
  12         58  
17             our @EXPORT_OK = qw();
18             our @EXPORT = qw(write_module write_index find_and_pack);
19              
20             #pod =func C
21             #pod
22             #pod # write_module( $source, $module, $libdir?, $metadata? );
23             #pod
24             #pod write_module( "./foo.js", "Foo::Bar", "./" );
25             #pod
26             #pod # ./Foo/Bar.pm now contains a uuencoded copy of foo.js
27             #pod
28             #pod Given a source asset path, a module name and a library directory, packs the
29             #pod source into a module named C<$module> and saves it in the right place relative
30             #pod to C<$libdir>
31             #pod
32             #pod Later, getting the file is simple:
33             #pod
34             #pod use Foo::Bar;
35             #pod print $Foo::Bar::content; # File Content is a string.
36             #pod
37             #pod =head3 options:
38             #pod
39             #pod =over 4
40             #pod
41             #pod =item C<$source> - A path describing where the asset is found
42             #pod
43             #pod =item C<$module> - A target name for the generated module
44             #pod
45             #pod =item C<$libdir> B<[optional]> - A target directory to serve as a base for modules.
46             #pod
47             #pod Defaults to C<./lib>.
48             #pod
49             #pod =item C<$metadata> B<[optional]> - A C payload of additional data to store in the module.
50             #pod
51             #pod =back
52             #pod
53             #pod =cut
54              
55             sub write_module {
56 6     6 1 205964 my ( $source, $module, $libdir, $metadata ) = @_;
57 6         21 my $dest = _module_full_path( $module, $libdir );
58 6         160 $dest->parent->mkpath; # mkdir
59 6         1551 $dest->spew_utf8( _pack_asset( $module, $source, $metadata ) );
60 6         7304 return;
61             }
62              
63             #pod =func C
64             #pod
65             #pod # write_index( $index, $module, $libdir?, $metadata? );
66             #pod
67             #pod write_index( { "A" => "X.js" }, "Foo::Bar", "./" );
68             #pod
69             #pod Creates a file index. This allows creation of a map of:
70             #pod
71             #pod "Module::Name" => "/Source/Path"
72             #pod
73             #pod Entries that will be available in a constructed module as follows:
74             #pod
75             #pod use Module::Name;
76             #pod $Module::Name::index->{"Module::Name"} # A String Path
77             #pod
78             #pod These generated files do B have a C<__DATA__> section
79             #pod
80             #pod =head3 options:
81             #pod
82             #pod =over 4
83             #pod
84             #pod =item C<$source> - A path describing where the asset is found
85             #pod
86             #pod =item C<$module> - A target name for the generated module
87             #pod
88             #pod =item C<$libdir> B<[optional]> - A target directory to serve as a base for modules.
89             #pod
90             #pod Defaults to C<./lib>.
91             #pod
92             #pod =item C<$metadata> B<[optional]> - A C payload of additional data to store in the module.
93             #pod
94             #pod =back
95             #pod
96             #pod =cut
97              
98             sub write_index {
99 3     3 1 381 my ( $index, $module, $libdir, $metadata ) = @_;
100 3         9 my $dest = _module_full_path( $module, $libdir );
101 3         102 $dest->parent->mkpath;
102 3         612 $dest->spew_utf8( _pack_index( $module, $index, $metadata ) );
103 3         1555 return;
104             }
105              
106             #pod =func C
107             #pod
108             #pod # find_and_pack( $root_dir, $namespace_prefix, $libdir? ) -> Hash
109             #pod
110             #pod Creates copies of all the contents of C<$root_dir> and constructs
111             #pod ( or reconstructs ) the relevant modules using C<$namespace_prefix>
112             #pod and stores them in C<$libdir> ( which defaults to C<./lib/> )
113             #pod
114             #pod B:
115             #pod Also generates an "index" file ( See L<< C|/write_index >> ) at the name C<$namespace_prefix>.
116             #pod
117             #pod Returns a hash detailing operations and results:
118             #pod
119             #pod {
120             #pod ok => [ { module => ..., file => ... }, ... ],
121             #pod unchanged => [ { module => ..., file => ... }, ... ],
122             #pod fail => [ { module => ..., file => ..., error => ... }, ... ],
123             #pod }
124             #pod
125             #pod Index updates will be in above list except with C<< index => 1 >> instead of C<< file => >>
126             #pod
127             #pod =head3 options:
128             #pod
129             #pod =over 4
130             #pod
131             #pod =item C<$root_dir> - The base path where the assets to be packed are stored
132             #pod
133             #pod =item C<$namespace_prefix> - A module name like C which will be used as the parent for generated modules.
134             #pod
135             #pod =item C<$libdir> B<[optional]> - The target directory to generate the Modules in.
136             #pod
137             #pod Defaults to C<./lib>.
138             #pod
139             #pod =back
140             #pod
141             #pod =cut
142              
143             sub find_and_pack {
144 3     3 1 3409 my ( $dir, $ns, $libdir ) = @_;
145 3         11 my %assets = _find_assets( $dir, $ns );
146 3         115 my ( @ok, @fail, @unchanged );
147 3         15 while ( my ( $module, $file ) = each %assets ) {
148 3         10 my $m = path( _module_full_path( $module, $libdir ) );
149 3         75 my $file_path = path($file)->absolute($dir); # Unconvert from relative.
150 3     3   172 my $fd = try { $file_path->stat->mtime } catch { 0 };
  3         156  
  0         0  
151 3     2   11251 my $md = try { $m->stat->mtime } catch { 0 };
  3         82  
  2         685  
152 3 100 66     121 if ( $md > 0 and $fd <= $md ) {
153 1         4 push @unchanged, { module => $module, module_path => $m, file => "$file", file_path => $file_path };
154 1         9 next;
155             }
156             try {
157 2     2   46 write_module( $file_path, $module, $libdir );
158 2         7 push @ok, { module => $module, module_path => $m, file => "$file", file_path => $file_path };
159             }
160             catch {
161 0     0   0 push @fail, { module => $module, module_path => $m, file => "$file", file_path => $file_path, error => $_ };
162 2         15 };
163             }
164 3         70 my $index_path = path( _module_full_path( $ns, $libdir ) );
165 3         96 my $index_return = { module => $ns, module_path => $index_path, index => 1 };
166              
167 3 50 66     21 if (@fail) {
    50          
    100          
168              
169             # Any fails -> No Attempt
170 0         0 $index_return->{error} = 'A module failed prior to index generation';
171 0         0 push @fail, $index_return;
172             }
173             elsif ( not @ok and not -e $index_path ) {
174              
175             # No "ok" results only generate index if one does not exist.
176 0         0 write_index( \%assets, $ns, $libdir, );
177 0         0 push @ok, $index_return;
178             }
179             elsif (@ok) {
180              
181             # Any ok results generate index.
182 2         6 write_index( \%assets, $ns, $libdir, );
183 2         4 push @ok, $index_return;
184             }
185             else {
186 1         21 push @unchanged, $index_return;
187             }
188 3         23 return { ok => \@ok, fail => \@fail, unchanged => \@unchanged };
189             }
190              
191             sub _modulify {
192 4     4   15 my ( $path, $namespace ) = @_;
193 4         13 $path =~ s/[^[:lower:]]//gi;
194 4         40 return $namespace . q[::] . $path;
195             }
196              
197             sub _module_rel_path {
198 23     23   965 my ($module) = @_;
199 23         92 $module =~ s{::}{/}g;
200 23         101 return "${module}.pm";
201             }
202              
203             sub _module_full_path {
204 21     21   2517 my ( $module, $libdir ) = @_;
205 21 50       56 $libdir = './lib' if not defined $libdir;
206 21         50 return path($libdir)->child( _module_rel_path($module) );
207             }
208              
209             sub _pack_asset {
210 8     8   192873 my ( $module, $path, $metadata ) = @_;
211 8         27 my $content = pack 'u', path($path)->slurp_raw;
212 8         2379 my $metadata_header = _pack_metadata($metadata);
213              
214 8         314 return <<"EOF";
215             use strict;
216             use warnings;
217             package $module;
218             $metadata_header
219             our \$content = do { local \$/; };
220             close *DATA;
221             \$content =~ s/\\s+//g;
222             \$content = unpack 'u', \$content;
223             1;
224             __DATA__