File Coverage

blib/lib/File/Assets.pm
Criterion Covered Total %
statement 305 330 92.4
branch 112 170 65.8
condition 60 107 56.0
subroutine 43 46 93.4
pod 18 24 75.0
total 538 677 79.4


line stmt bran cond sub pod time code
1             package File::Assets;
2              
3 24     24   579532 use warnings;
  24         54  
  24         817  
4 24     24   131 use strict;
  24         40  
  24         1851  
5              
6             =head1 NAME
7              
8             File::Assets - Manage .css and .js assets for a web page or application
9              
10             =head1 VERSION
11              
12             Version 0.064_1
13              
14             =cut
15              
16             our $VERSION = '0.064_1';
17              
18             =head1 SYNOPSIS
19              
20             use File::Assets
21              
22             my $assets = File::Assets->new( base => [ $uri_root, $dir_root ] )
23              
24             # Put minified files in $dir_root/built/... (the trailing slash is important)
25             $assets->set_output_path("built/")
26              
27             # File::Assets will automatically detect the type based on the extension
28             $assets->include("/static/style.css")
29              
30             # You can also include external assets:
31             $assets->include("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js");
32              
33             # This asset won't get included twice, as File::Assets will ignore repeats of a path
34             $assets->include("/static/style.css")
35              
36             # And finally ...
37             $assets->export
38              
39             # Or you can iterate (in order)
40             for my $asset ($assets->exports) {
41            
42             print $asset->uri, "\n";
43              
44             }
45              
46             In your .tt (Template Toolkit) files:
47              
48             [% WRAPPER page.tt %]
49              
50             [% assets.include("/static/special-style.css", 100) %] # The "100" is the rank, which makes sure it is exported after other assets
51              
52             [% asset = BLOCK %]
53            
56             [% END %]
57             [% assets.include(asset) %] # This will include the css into an inline asset with the media type of "print"
58              
59             # ... finally, in your "main" template:
60              
61             [% CLEAR -%]
62            
63              
64            
65             [% assets.export("css") %]
66            
67              
68            
69              
70             [% content %]
71              
72            
73              
74             [% assets.export("js") %]
75              
76            
77              
78            
79              
80             Use the minify option to perform minification before export
81              
82             my $assets = File::Assets->new( minify => 1, ... )
83              
84             =head1 DESCRIPTION
85              
86             File::Assets is a tool for managing JavaScript and CSS assets in a (web) application. It allows you to "publish" assests in one place after having specified them in different parts of the application (e.g. throughout request and template processing phases).
87              
88             This package has the added bonus of assisting with minification and filtering of assets. Support is built-in for YUI Compressor (L), L, L, L, and L.
89              
90             File::Assets was built with L in mind, although this package is framework agnostic. Look at L for an easy way to integrate File::Assets with Catalyst.
91              
92             =head1 USAGE
93              
94             =head2 Cascading style sheets and their media types
95              
96             A cascading style sheet can be one of many different media types. For more information, look here: L
97              
98             This can cause a problem when minifying, since, for example, you can't bundle a media type of screen with a media type of print. File::Assets handles this situation by treating .css files of different media types separately.
99              
100             To control the media type of a text/css asset, you can do the following:
101              
102             $assets->include("/path/to/printstyle.css", ..., { media => "print" }); # The asset will be exported with the print-media indicator
103              
104             $assets->include_content($content, "text/css", ..., { media => "screen" }); # Ditto, but for the screen type
105              
106             =head2 Including assets in the middle of processing a Template Toolkit template
107              
108             Sometimes, in the middle of a TT template, you want to include a new asset. Usually you would do something like this:
109              
110             [% assets.include("/include/style.css") %]
111              
112             But then this will show up in your output, because ->include returns an object:
113              
114             File::Assets::Asset=HASH(0x99047e4)
115              
116             The way around this is to use the TT "CALL" directive, as in the following:
117              
118             [% CALL assets.include("/include/style.css") %]
119              
120             =head2 Avoid minifying assets on every request (if you minify)
121              
122             By default, File::Assets will avoid re-minifying assets if nothing in the files have changed. However, in a web application, this can be a problem if you serve up two web pages that have different assets. That's because File::Assets will detect different assets being served in page A versus assets being served in page B (think AJAX interface vs. plain HTML with some CSS). The way around this problem is to name your assets object with a unique name per assets bundle. By default, the name is "assets", but can be changed with $assets->name():
123              
124             my $assets = File::Assets->new(...);
125             $assets->name("standard");
126              
127             You can change the name of the assets at anytime before exporting.
128              
129             =head2 YUI Compressor 2.2.5 is required
130              
131             If you want to use the YUI Compressor, you should have version 2.2.5 or above.
132              
133             YUI Compressor 2.1.1 (and below) will *NOT WORK*
134              
135             To use the compressor for minification specify the path to the .jar like so:
136              
137             my $assets = File::Assets->new( minify => "/path/to/yuicompressor.jar", ... )
138              
139             =head2 Specifying an C pattern
140              
141             When aggregating or minifying assets, you need to put the result in a new file.
142              
143             You can use the following directives when crafting a path/filename pattern:
144              
145             %n The name of the asset, "assets" by default
146             %e The extension of the asset (e.g. css, js)
147             %f The fingerprint of the asset collection (a hexadecimal digest of the concatenated digest of each asset in the collection)
148             %k The kind of the asset (e.g. css-screen, css, css-print, js)
149             %h The kind head-part of the asset (e.g. css, js)
150             %l The kind tail-part of the asset (e.g. screen, print) (essentially the media type of a .css asset)
151              
152             In addition, in each of the above, a ".", "/" or "-" can be placed in between the "%" and directive character.
153             This will result in a ".", "/", or "-" being prepended to the directive value.
154              
155             The default pattern is:
156              
157             %n%-l%-f.%e
158              
159             A pattern of C<%n%-l.%e> can result in the following:
160              
161             assets.css # name of "assets", no media type, an asset type of CSS (.css)
162             assets-screen.css # name of "assets", media type of "screen", an asset type of CSS (.css)
163             assets.js # name of "assets", an asset type of JavaScript (.js)
164              
165             If the pattern ends with a "/", then the default pattern will be appended
166              
167             xyzzy/ => xyzzy/%n%-l-%f.%e
168              
169             If the pattern does not have an extension-like ending, then "%.e" will be appended
170              
171             xyzzy => xyzzy.%e
172              
173             =head2 Strange output or "sticky" content
174              
175             File::Assets uses built-in caching to share content across different objects (via File::Assets::Cache). If you're having problems
176             try disabling the cache by passing "cache => 0" to File::Assets->new
177              
178             =head1 METHODS
179              
180             =cut
181              
182             # If the pattern does NOT begin with a "/", then the base dir will be prepended
183              
184 24     24   115 use strict;
  24         57  
  24         607  
185 24     24   107 use warnings;
  24         43  
  24         876  
186              
187 24     24   22319 use Object::Tiny qw/cache registry _registry_hash rsc filter_scheme output_path_scheme output_asset_scheme/;
  24         8405  
  24         143  
188 24     24   286598 use File::Assets::Carp;
  24         80  
  24         169  
189              
190 24     24   25699 use Tie::LLHash;
  24         38333  
  24         726  
191 24     24   20975 use Path::Resource;
  24         4586868  
  24         525  
192 24     24   1216 use Scalar::Util qw/blessed refaddr/;
  24         43  
  24         1428  
193 24     24   23824 use HTML::Declare qw/LINK SCRIPT STYLE/;
  24         1866237  
  24         3212  
194 24     24   264 use File::Copy();
  24         43  
  24         399  
195              
196 24     24   16123 use File::Assets::Asset;
  24         96  
  24         220  
197 24     24   15462 use File::Assets::Cache;
  24         68  
  24         199  
198 24     24   12404 use File::Assets::Kind;
  24         72  
  24         212  
199 24     24   12618 use File::Assets::Bucket;
  24         89  
  24         209  
200              
201             =head2 File::Assets->new( base => , output_path => , minify => )
202              
203             Create and return a new File::Assets object.
204              
205             You can configure the object with the following:
206            
207             base # A hash reference with a "uri" key/value and a "dir" key/value.
208             For example: { uri => http://example.com/assets, dir => /var/www/htdocs/assets }
209            
210             # A URI::ToDisk object
211              
212             # A Path::Resource object
213              
214             minify # "1" or "best" - Will either use JavaScript::Minifier::XS> & CSS::Minifier::XS or
215             JavaScript::Minifier> & CSS::Minifier (depending on availability)
216             for minification
217              
218             # "0" or "" or undef - Don't do any minfication (this is the default)
219              
220             # "./path/to/yuicompressor.jar" - Will use YUI Compressor via the given .jar for minification
221              
222             # "minifier" - Will use JavaScript::Minifier & CSS::Minifier for minification
223              
224             # "xs" or "minifier-xs" - Will use JavaScript::Minifier::XS & CSS::Minifier::XS for minification
225              
226             output_path # Designates the output path for minified .css and .js assets
227             The default output path pattern is "%n%-l%-d.%e" (rooted at the dir of )
228             See above in "Specifying an output_path pattern" for details
229              
230             =cut
231              
232             sub new {
233 41     41 1 2231261 my $self = bless {}, shift;
234 41         283 local %_ = @_;
235              
236 41   33     749 $self->set_base($_{rsc} || $_{base_rsc} || $_{base});
237 41 50 0     238112 $self->set_base_uri($_{uri} || $_{base_uri}) if $_{uri} || $_{base_uri};
      33        
238 41 50 0     331 $self->set_base_dir($_{dir} || $_{base_dir}) if $_{dir} || $_{base_dir};
      33        
239 41 50       166 $self->set_base_path($_{base_path}) if $_{base_path};
240              
241 41   100     477 $self->set_output_path($_{output_path} || $_{output_path_scheme} || []);
242              
243 41         292 $self->name($_{name});
244            
245 41 50       261 $_{cache} = 1 unless exists $_{cache};
246 41 50       273 $self->set_cache($_{cache}) if $_{cache};
247              
248             # my $rsc = File::Assets::Util->parse_rsc($_{rsc} || $_{base_rsc} || $_{base});
249             # $rsc->uri($_{uri} || $_{base_uri}) if $_{uri} || $_{base_uri};
250             # $rsc->dir($_{dir} || $_{base_dir}) if $_{dir} || $_{base_dir};
251             # $rsc->path($_{base_path}) if $_{base_path};
252             # $self->{rsc} = $rsc;
253              
254 41         67 my %registry;
255 41         447 $self->{registry} = tie(%registry, qw/Tie::LLHash/, { lazy => 1 });
256 41         1181 $self->{_registry_hash} = \%registry;
257              
258 41         126 $self->{filter_scheme} = {};
259 41   100     504 my $filter_scheme = $_{filter} || $_{filters} || $_{filter_scheme} || [];
260 41         148 for my $rule (@$filter_scheme) {
261 5         32 $self->filter(@$rule);
262             }
263              
264 41 100       165 if (my $minify = $_{minify}) {
265 16 100 66     309 if ($minify eq 1 || $minify =~ m/^\s*(?:minifier-)?best\s*$/i) { $self->filter("minifier-best") }
  1 50       5  
    50          
    100          
    50          
    50          
266 0         0 elsif ($minify =~ m/^\s*yui-?compressor:/) { $self->filter($minify) }
267 0         0 elsif ($minify =~ m/\.jar/i) { $self->filter("yuicompressor:$minify") }
268 1         5 elsif ($minify =~ m/^\s*(?:minifier-)?xs\s*$/i) { $self->filter("minifier-xs") }
269 0         0 elsif ($minify =~ m/^\s*minifier\s*$/i) { $self->filter("minifier") }
270 14         60 elsif ($minify =~ m/^\s*concat\s*$/i) { $self->filter("concat") }
271 0         0 else { croak "Don't understand minify option ($minify)" }
272             }
273              
274 41         319 return $self;
275             }
276              
277             =head2 $asset = $assets->include(, [ , , { ... } ])
278              
279             =head2 $asset = $assets->include_path(, [ , , { ... } ])
280              
281             First, if is a scalar reference or "looks like" some HTML (starts with a angle bracket, e.g.: ), then
282             it will be treated as inline content.
283              
284             Otherwise, this will include an asset located at "/" for processing. The asset will be exported as "/"
285              
286             Optionally, you can specify a rank, where a lower number (i.e. -2, -100) causes the asset to appear earlier in the exports
287             list, and a higher number (i.e. 6, 39) causes the asset to appear later in the exports list. By default, all assets start out
288             with a neutral rank of 0.
289              
290             Also, optionally, you can specify a type override as the third argument.
291              
292             By default, the newly created $asset is NOT inline.
293              
294             Returns the newly created asset.
295              
296             NOTE: See below for how the extra hash on the end is handled
297              
298             =head2 $asset = $assets->include({ ... })
299              
300             Another way to invoke include is by passing in a hash reference.
301              
302             The hash reference should contain the follwing information:
303            
304             path # The path to the asset file, relative to base
305             content # The content of the asset
306              
307             type # Optional if a path is given, required for content
308             rank # Optional, 0 by default (Less than zero is earlier, greater than zero is later)
309             inline # Optional, by default true if content was given, false is a path was given
310             base # Optional, by default the base of $assets
311              
312             You can also pass extra information through the hash. Any extra information will be bundled in the ->attributes hash of $asset.
313             For example, you can control the media type of a text/css asset by doing something like:
314              
315             $assets->include("/path/to/printstyle.css", ..., { media => "print" }) # The asset will be exported with the print-media indicator
316              
317             NOTE: The order of and doesn't really matter, since we can detect whether something looks like a rank (number) or
318             not, and correct for it (and it does).
319              
320             =cut
321              
322             sub include_path {
323 0     0 1 0 my $self = shift;
324 0         0 return $self->include(@_);
325             }
326              
327             my $rankish = qr/^[\-\+]?[\.\d]+$/; # A regular expression for a string that looks like a rank
328             sub _correct_for_proper_rank_and_type_order ($) {
329 123     123   193 my $asset = shift;
330 123 50 66     1113 if (defined $asset->{type} && $asset->{type} =~ $rankish ||
      66        
      33        
331             defined $asset->{rank} && $asset->{rank} !~ $rankish) {
332             # Looks like someone entered a rank as the type or vice versa, so we'll switch them
333 0         0 my $rank = delete $asset->{type};
334 0         0 my $type = delete $asset->{rank};
335 0 0       0 $asset->{type} = $type if defined $type;
336 0 0       0 $asset->{rank} = $rank if defined $rank;
337             }
338             }
339              
340             sub include {
341 118     118 1 2087074 my $self = shift;
342              
343 118         204 my (@asset, $path);
344 118 50       443 if (ref $_[0] ne "HASH") {
345 118         212 $path = shift;
346 118 50 33     690 croak "Don't have a path to include" unless defined $path && length $path;
347 118 100 100     707 if (ref $path eq "SCALAR" || $path =~ m/^\s*
348 3         8 push @asset, content => $path;
349             }
350             else {
351 115 100       408 return $self->fetch($path) if $self->exists($path);
352 114         1969 push @asset, path => $path;
353             }
354             }
355              
356 117         295 for (qw/rank type/) {
357 124 100 100     695 last if ! @_ || ref $_[0] eq "HASH";
358 8         22 push @asset, $_ => shift;
359             }
360 117 100 66     443 push @asset, %{ $_[0] } if @_ && ref $_[0] eq "HASH";
  4         15  
361 117         364 my %asset = @asset;
362 117         376 _correct_for_proper_rank_and_type_order \%asset;
363              
364 117         2956 my $asset = File::Assets::Asset->new(base => $self->rsc, cache => $self->cache, %asset);
365              
366 117         618 return $self->fetch_or_store($asset);
367             }
368              
369             =head2 $asset = $assets->include_content(, [ , , { ... } ])
370              
371             Include an asset with some content and of the supplied type. The value of can be a "plain" string or a scalar reference.
372              
373             You can include content that looks like HTML:
374              
375            
380              
381             In the above case, is optional, as File::Assets can detect from the tag that you're supplying a style sheet. Furthermore,
382             the method will find all the attributes in the tag and put them into the asset. So the resulting asset from including the above
383             will have a type of "text/css" and media of "print".
384              
385             For now, only