File Coverage

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   342067 use 5.006; # our
  12         42  
2 12     12   56 use strict;
  12         16  
  12         275  
3 12     12   60 use warnings;
  12         17  
  12         576  
4              
5             package Asset::Pack;
6              
7 12     12   6622 use Path::Tiny 0.069 qw( path ); # path()->visit without broken ref returns
  12         85048  
  12         918  
8 12     12   7410 use Try::Tiny qw( try catch );
  12         15154  
  12         1064  
9              
10             our $VERSION = '0.000006';
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   3994 use parent qw(Exporter);
  12         2294  
  12         70  
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 250063 my ( $source, $module, $libdir, $metadata ) = @_;
57 6         21 my $dest = _module_full_path( $module, $libdir );
58 6         179 $dest->parent->mkpath; # mkdir
59 6         1937 $dest->spew_utf8( _pack_asset( $module, $source, $metadata ) );
60 6         7154 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 413 my ( $index, $module, $libdir, $metadata ) = @_;
100 3         11 my $dest = _module_full_path( $module, $libdir );
101 3         135 $dest->parent->mkpath;
102 3         879 $dest->spew_utf8( _pack_index( $module, $index, $metadata ) );
103 3         2631 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 228973 my ( $dir, $ns, $libdir ) = @_;
145 3         14 my %assets = _find_assets( $dir, $ns );
146 3         132 my ( @ok, @fail, @unchanged );
147 3         15 while ( my ( $module, $file ) = each %assets ) {
148 3         12 my $m = path( _module_full_path( $module, $libdir ) );
149 3         100 my $file_path = path($file)->absolute($dir); # Unconvert from relative.
150 3     1   232 my $fd = try { $file_path->stat->mtime } catch { 0 };
  3         143  
  0         0  
151 3     3   800250 my $md = try { $m->stat->mtime } catch { 0 };
  3         107  
  2         844  
152 3 100 66     155 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   63 write_module( $file_path, $module, $libdir );
158 2         12 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         24 };
163             }
164 3         76 my $index_path = path( _module_full_path( $ns, $libdir ) );
165 3         98 my $index_return = { module => $ns, module_path => $index_path, index => 1 };
166              
167 3 50 66     23 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         12 write_index( \%assets, $ns, $libdir, );
183 2         4 push @ok, $index_return;
184             }
185             else {
186 1         24 push @unchanged, $index_return;
187             }
188 3         27 return { ok => \@ok, fail => \@fail, unchanged => \@unchanged };
189             }
190              
191             sub _modulify {
192 4     4   15 my ( $path, $namespace ) = @_;
193 4         14 $path =~ s/[^[:lower:]]//gi;
194 4         46 return $namespace . q[::] . $path;
195             }
196              
197             sub _module_rel_path {
198 23     23   807 my ($module) = @_;
199 23         86 $module =~ s{::}{/}g;
200 23         103 return "${module}.pm";
201             }
202              
203             sub _module_full_path {
204 21     21   2616 my ( $module, $libdir ) = @_;
205 21 50       64 $libdir = './lib' if not defined $libdir;
206 21         67 return path($libdir)->child( _module_rel_path($module) );
207             }
208              
209             sub _pack_asset {
210 8     8   209763 my ( $module, $path, $metadata ) = @_;
211 8         32 my $content = pack 'u', path($path)->slurp_raw;
212 8         3555 my $metadata_header = _pack_metadata($metadata);
213              
214 8         302 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__