The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
=head1 NAME

Dancer::Plugin::Assets - Manage and minify .css and .js assets in a Dancer application

=head1 SYNOPSIS

=head3 In your Dancer application

 use Dancer::Plugin::Assets qw( assets add_asset );

=head3 Sometime during the request ...

 get "/index" => sub {
    ## Include assets by plugin method
    add_asset "/css/beautiful.css";
    add_asset "/css/handlebars.js";

    ## Include assets by assets object
    assets->include( "/css/main.css" );
    assets->include( "/js/something.js" );
 };

=head3 Then, in your .tt, print css tags at <head>, print js tags after body

  <html>
    <head><title>[% title %]</title>
    [% css_tags %]
    </head>
    <body>
    </body>
    [% js_tags %]
  </html>

=head3 Or you want to add css and js and print them all in template file inside <head>

  <html>
    <head><title>[% title %]</title>
    [% add_asset("/js/jquery.js")         %]
    [% add_asset("/js/handlebars.js")     %]
    [% add_asset("/css/beautiful.css")    %]
    [% CALL assets.include("/js/foo.js")  %]
    [% css_and_js_tags || js_and_css_tags %]
    </head>
    <body>
    </body>
  </html>

=head1 DESCRIPTION

Dancer::Plugin::Assets integrates File::Assets into your Dancer application. Essentially, it provides a unified way to include .css and .js assets from different parts of your program. When you're done processing a request, you can use assets->export() or [% css_and_js_tags %] to generate HTML or assets->exports() to get a list of assets.

D::P::Assets will also handle .css files of different media types properly.

In addition, D::P::Assets includes support for minification via YUI compressor, L<JavaScript::Minifier>, L<CSS::Minifier>, L<JavaScript::Minifier::XS>, and L<CSS::Minifier::XS>

Note that Dancer::Plugin::Assets does not serve files directly, it will work with L<Static::Simple> or whatever static-file-serving mechanism you're using.

=head1 USEAGE

For usage hints and tips, see L<File::Assets>

=head1 CONFIGURATION

You can configure D::P::Assets by manipulating the environment configration files, e.g.

    config.yml
    environments/development.yml
    environments/production.yml

The following settings are available:

    url            # The url to access the asset files default "/"

    base_dir       # A path to automatically look for assets under (e.g. "/public")
                   
                   # This path will be automatically prepended to includes, so that instead of
                   # doing ->include("/public/css/stylesheet.css") you can just do ->include("/css/stylesheet.css")
                   
                   
    output_dir     # The path to output the results of minification under (if any).
                   # For example, if output is "built/" (the trailing slash is important), then minified assets will be
                   # written to "root/<assets-path>/static/..."
                   #
                   # Designates the output path for minified .css and .js assets
                   # The default output path pattern is "%n%-l%-d.%e" (rooted at the dir of <base>)
                   
                   
    minify         # "1" or "best" - Will either use JavaScript::Minifier::XS> & CSS::Minifier::XS or
                   #                 JavaScript::Minifier> & CSS::Minifier (depending on availability)
                   #                 for minification
                   # "0" or "" or undef - Don't do any minfication (this is the default)
                   # "./path/to/yuicompressor.jar" - Will use YUI Compressor via the given .jar for minification
                   # "minifier" - Will use JavaScript::Minifier & CSS::Minifier for minification


    minified_name  # The name of the key in the stash that provides the assets object (accessible via config->{plugins}{assets}{minified_name}.
                   # By default, the <minified_name> is "minified".

=head1 Example configuration

Here is an example configuration: ( All the value are set by default )

    plugins:
        Assets:
            base_dir: "/public"
            output_dir: "static/%n%-l.%e"
            minified_name: "minified"
            minify: 1

=head1 METHODS IN ROUTE CODE

=head2 assets

Return L<File::Assets> object that exists throughout the lifetime of the request

=head2 add_asset

same as sub { assets->include( shift ); return undef }

=head1 METHODS IN TT

=head2 [% assets %]

Return L<File::Assets> object that exists throughout the lifetime of the request

=head2 [% add_asset %]

same as [% CALL assets.include( "file..." ) %]

=head2 [% css_tags %]

return tags of css in html

=head2 [% js_tags %]

return tags of javascript in html

=head2 [% css_and_js_tags %]

return tags in this order of css and javascript in html

=head2 [% js_and_css_tags %]

return tags in this order of javascript and css in html

=head1 AUTHOR

Michael Vu, C<< <micvu at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-dancer-plugin-assets at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dancer::Plugin::Assets>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Dancer::Plugin::Assets

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dancer-Plugin-Assets>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Dancer-Plugin-Assets>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Dancer-Plugin-Assets>

=item * Search CPAN

L<http://search.cpan.org/dist/Dancer-Plugin-Assets>

=item * GIT Respority

L<https://bitbucket.org/mvu8912/p5-dancer-plugin-assets>

=back

=head1 SEE ALSO

L<File::Assets>

L<Dancer::Plugin>

L<http://developer.yahoo.com/yui/compressor/>

L<JavaScript::Minifier::XS>

L<CSS::Minifier::XS>

L<JavaScript::Minifier>

L<CSS::Minifier>

=head1 ACKNOWLEDGEMENTS

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

package Dancer::Plugin::Assets;
{
  $Dancer::Plugin::Assets::VERSION = '1.5';
}
use URI;
use Dancer::Plugin;
use Dancer ":syntax";
use File::Assets;

register assets              => \&_assets;
register add_asset           => \&_include;
register_plugin for_versions => [ 1, 2 ];

hook before_template_render => sub {
    my $stash = shift;
    $stash->{assets}          = _assets();
    $stash->{add_asset}       = \&_include;
    $stash->{css_tags}        = \&_css_tags;
    $stash->{js_tags}         = \&_js_tags;
    $stash->{css_and_js_tags} = \&_css_and_js_tags;
    $stash->{js_and_css_tags} = \&_js_and_css_tags;
};

sub _assets {
    return var("assets")
      || _build_assets();
}

sub _include {
    my $assets = _assets();
    $assets->include(@_);
    return;
}

sub _js_and_css_tags {
    return _js_tags() . _css_tags();
}

sub _css_and_js_tags {
    return _css_tags() . _js_tags();
}

sub _css_tags {
    my $assets = _assets();
    return $assets->export("css");
}

sub _js_tags {
    my $assets = _assets();
    return $assets->export("js");
}

sub _build_assets {
    my $setting = plugin_setting();

    my $url           = _url( $setting->{url} );
    my $base_dir      = $setting->{base_dir} || setting "public";
    my $output_dir    = $setting->{output_dir} || "static/%n%-l.%e";
    my $minify        = defined $setting->{minify} ? $setting->{minify} : 1;
    my $minified_name = $setting->{minified_name} || "minified";

    ## https://metacpan.org/pod/File::Assets#METHODS
    my $assets = File::Assets->new(
        name        => $minified_name,
        minify      => $minify,
        output_path => $output_dir,
        base        => [ $url, $base_dir ],
    );

    return var assets => $assets;
}

sub _url {
    my $url = shift
      or return _site_url();
    if ( $url =~ /^\// ) {
        return _site_url() . $url;
    }
    if ( $url !~/^[^\/]+\:\/\//i ) {
        return _site_url() . $url;
    }
    return $url;
}

sub _site_url {
    return _scheme() . "://" . _host();
}

sub _scheme {
    return request->env->{"psgi.url_scheme"};
}

sub _host {
    return request->host;
}

true;