Skip to content

Commit

Permalink
added support for multiple "templates" and "public" directories to Mo…
Browse files Browse the repository at this point in the history
…jolicious (closes #286)
  • Loading branch information
kraih committed Feb 12, 2012
1 parent cdb5788 commit 005568f
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 63 deletions.
8 changes: 7 additions & 1 deletion Changes
@@ -1,6 +1,12 @@
This file documents the revision history for Perl extension Mojolicious.

2.49 2012-02-12 00:00:00
2.49 2012-02-13 00:00:00
- Deprecated Mojolicious::Renderer->root in favor of
Mojolicious::Renderer->path. (memowe, sri)
- Deprecated Mojolicious::Static->root in favor of
Mojolicious::Static->path. (memowe, sri)
- Added support for multiple "templates" and "public" directories to
Mojolicious. (memowe, sri)
- Improved version command to be more responsive. (crab)
- Improved resilience of Mojo::IOLoop::Client.
- Improved documentation.
Expand Down
6 changes: 3 additions & 3 deletions lib/Mojolicious.pm
Expand Up @@ -59,10 +59,10 @@ sub DESTROY { }
sub new {
my $self = shift->SUPER::new(@_);

# Root directories
# Paths
my $home = $self->home;
$self->renderer->root($home->rel_dir('templates'));
$self->static->root($home->rel_dir('public'));
push @{$self->renderer->paths}, $home->rel_dir('templates');
push @{$self->static->paths}, $home->rel_dir('public');

# Default to application namespace
my $r = $self->routes;
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojolicious/Guides/Cookbook.pod
Expand Up @@ -1017,10 +1017,10 @@ get automatically installed with the modules.
File::Spec->catdir(dirname(__FILE__), 'MyMojoliciousApp'));

# Switch to installable "public" directory
$self->static->root($self->home->rel_dir('public'));
$self->static->paths->[0] = $self->home->rel_dir('public');

# Switch to installable "templates" directory
$self->renderer->root($self->home->rel_dir('templates'));
$self->renderer->paths->[0] = $self->home->rel_dir('templates');

$self->plugin('PODRenderer');

Expand Down
42 changes: 30 additions & 12 deletions lib/Mojolicious/Renderer.pm
Expand Up @@ -17,7 +17,7 @@ has encoding => 'UTF-8';
has handlers => sub { {} };
has helpers => sub { {} };
has layout_prefix => 'layouts';
has root => '/';
has paths => sub { [] };

# "This is not how Xmas is supposed to be.
# In my day Xmas was about bringing people together,
Expand Down Expand Up @@ -157,6 +157,18 @@ sub render {
return $output, $c->app->types->type($format) || 'text/plain';
}

# DEPRECATED in Leaf Fluttering In Wind!
sub root {
warn <<EOF;
Mojolicious::Renderer->root is DEPRECATED in favor of
Mojolicious::Renderer->paths!
EOF
my $self = shift;
return $self->paths->[0] unless @_;
$self->paths->[0] = shift;
return $self;
}

sub template_name {
my ($self, $options) = @_;

Expand All @@ -171,8 +183,18 @@ sub template_name {

sub template_path {
my $self = shift;

# Nameless
return unless my $name = $self->template_name(shift);
return File::Spec->catfile($self->root, split '/', $name);

# Search all paths
foreach my $path (@{$self->paths}) {
my $file = File::Spec->catfile($path, split '/', $name);
return $file if -r $file;
}

# Fall back to first path
return File::Spec->catfile($self->paths->[0], split '/', $name);
}

sub _detect_handler {
Expand All @@ -185,7 +207,7 @@ sub _detect_handler {
my $templates = $self->{templates};
unless ($templates) {
$templates = $self->{templates} =
Mojo::Home->new->parse($self->root)->list_files;
[map { @{Mojo::Home->new($_)->list_files} } @{$self->paths}];
}

# DATA templates
Expand All @@ -203,8 +225,6 @@ sub _detect_handler {
return;
}

# "You are hereby conquered.
# Please line up in order of how much beryllium it takes to kill you."
sub _detect_template_class {
my ($self, $options) = @_;
return $options->{template_class} || $self->default_template_class;
Expand All @@ -219,8 +239,6 @@ sub _extends {
return delete $stash->{extends};
}

# "Oh no! Can we switch back using four or more bodies?
# I'm not sure. I'm afraid we need to use... MATH."
sub _render_template {
my ($self, $c, $output, $options) = @_;

Expand Down Expand Up @@ -341,12 +359,12 @@ Registered helpers.
Directory to look for layouts in, defaults to C<layouts>.
=head2 C<root>
=head2 C<paths>
my $root = $renderer->root;
$renderer = $renderer->root('/foo/bar/templates');
my $paths = $renderer->paths;
$renderer = $renderer->paths(['/foo/bar/templates']);
Directory to look for templates in.
Directories to look for templates in.
=head1 METHODS
Expand Down Expand Up @@ -382,7 +400,7 @@ sample helpers.
template_class => 'main'
}, 'foo.html.ep');
Get an DATA template by name, usually used by handlers.
Get a DATA template by name, usually used by handlers.
=head2 C<render>
Expand Down
88 changes: 48 additions & 40 deletions lib/Mojolicious/Static.pm
Expand Up @@ -10,7 +10,7 @@ use Mojo::Home;
use Mojo::Path;

has default_static_class => 'main';
has 'root';
has paths => sub { [] };

# "Valentine's Day's coming? Aw crap! I forgot to get a girlfriend again!"
sub dispatch {
Expand Down Expand Up @@ -39,75 +39,82 @@ sub dispatch {
return 1;
}

# DEPRECATED in Leaf Fluttering In Wind!
sub root {
warn <<EOF;
Mojolicious::Static->root is DEPRECATED in favor of
Mojolicious::Static->paths!
EOF
my $self = shift;
return $self->paths->[0] unless @_;
$self->paths->[0] = shift;
return $self;
}

sub serve {
my ($self, $c, $rel) = @_;

# Path and extension
my $path = File::Spec->catfile($self->root, split('/', $rel));
$path =~ /\.(\w+)$/;
my $ext = $1;

# Root for bundled files
$self->{bundled} ||= Mojo::Home->new(Mojo::Home->mojo_lib_dir)
->rel_dir('Mojolicious/public');

# Normal file
# Search all paths
my $asset;
my $modified = $self->{modified} ||= time;
my $size = 0;
my $modified = $self->{modified} ||= time;
my $res = $c->res;
if (my $file = $self->_get_file($path)) {
if (@$file) { ($asset, $size, $modified) = @$file }
for my $path (@{$self->paths}) {
my $file = File::Spec->catfile($path, split('/', $rel));
next unless my $data = $self->_get_file($file);

# Exists but is forbidden
else {
# Forbidded
unless (@$data) {
$c->app->log->debug(qq/File "$rel" is forbidden./);
$res->code(403) and return;
}

# Exists
($asset, $size, $modified) = @$data;
}

# DATA file
elsif (!$asset && defined(my $data = $self->_get_data_file($c, $rel))) {
# Search DATA
if (!$asset && defined(my $data = $self->_get_data_file($c, $rel))) {
$size = length $data;
$asset = Mojo::Asset::Memory->new->add_chunk($data);
}

# Bundled file
else {
$path = File::Spec->catfile($self->{bundled}, split('/', $rel));
if (my $bundled = $self->_get_file($path)) {
($asset, $size, $modified) = @$bundled if @$bundled;
}
# Search bundled files
elsif (!$asset) {
my $b = $self->{bundled} ||= Mojo::Home->new(Mojo::Home->mojo_lib_dir)
->rel_dir('Mojolicious/public');
my $data = $self->_get_file(File::Spec->catfile($b, split('/', $rel)));
($asset, $size, $modified) = @$data if $data && @$data;
}

# Not a static file
return unless $asset;

# If modified since
my $rqh = $c->req->headers;
my $rsh = $res->headers;
if (my $date = $rqh->if_modified_since) {
my $req_headers = $c->req->headers;
my $res_headers = $res->headers;
if (my $date = $req_headers->if_modified_since) {

# Not modified
my $since = Mojo::Date->new($date)->epoch;
if (defined $since && $since == $modified) {
$rsh->remove('Content-Type');
$rsh->remove('Content-Length');
$rsh->remove('Content-Disposition');
$res_headers->remove('Content-Type');
$res_headers->remove('Content-Length');
$res_headers->remove('Content-Disposition');
$res->code(304) and return 1;
}
}

# Range
my $start = 0;
my $end = $size - 1 >= 0 ? $size - 1 : 0;
if (my $range = $rqh->range) {
if (my $range = $req_headers->range) {
if ($range =~ m/^bytes=(\d+)\-(\d+)?/ && $1 <= $end) {
$start = $1;
$end = $2 if defined $2 && $2 <= $end;
$res->code(206);
$rsh->content_length($end - $start + 1);
$rsh->content_range("bytes $start-$end/$size");
$res_headers->content_length($end - $start + 1);
$res_headers->content_range("bytes $start-$end/$size");
}

# Not satisfiable
Expand All @@ -119,9 +126,10 @@ sub serve {
# Serve file
$res->code(200) unless $res->code;
$res->content->asset($asset);
$rsh->content_type($c->app->types->type($ext) || 'text/plain');
$rsh->accept_ranges('bytes');
$rsh->last_modified(Mojo::Date->new($modified));
$rel =~ /\.(\w+)$/;
$res_headers->content_type($c->app->types->type($1) || 'text/plain');
$res_headers->accept_ranges('bytes');
$res_headers->last_modified(Mojo::Date->new($modified));

return 1;
}
Expand Down Expand Up @@ -183,12 +191,12 @@ L<Mojolicious::Static> implements the following attributes.
Class to use for finding files in C<DATA> section, defaults to C<main>.
=head2 C<root>
=head2 C<paths>
my $root = $static->root;
$static = $static->root('/foo/bar/files');
my $paths = $static->paths;
$static = $static->paths(['/foo/bar/public']);
Directory to serve static files from.
Directories to serve static files from.
=head1 METHODS
Expand Down
2 changes: 1 addition & 1 deletion t/mojo/websocket.t
Expand Up @@ -26,7 +26,7 @@ use Mojolicious::Lite;
app->log->level('fatal');

# Avoid exception template
app->renderer->root(app->home->rel_dir('public'));
app->renderer->paths->[0] = app->home->rel_dir('public');

# GET /link
get '/link' => sub {
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/exception_lite_app.t
Expand Up @@ -17,7 +17,7 @@ use Test::More tests => 80;
use Mojolicious::Lite;
use Test::Mojo;

app->renderer->root(app->home->rel_dir('does_not_exist'));
app->renderer->paths->[0] = app->home->rel_dir('does_not_exist');

# Logger
app->log->handle(undef);
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/layouted_lite_app.t
Expand Up @@ -19,7 +19,7 @@ use Test::Mojo;
# Plugin with a template
plugin 'PluginWithTemplate';

app->renderer->root(app->home->rel_dir('does_not_exist'));
app->renderer->paths->[0] = app->home->rel_dir('does_not_exist');

# Default layout for whole application
app->defaults(layout => 'default');
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/lib/MojoliciousTest.pm
Expand Up @@ -5,7 +5,7 @@ sub development_mode {
my $self = shift;

# Static root for development
$self->static->root($self->home->rel_dir('public_dev'));
$self->static->paths->[0] = $self->home->rel_dir('public_dev');
}

# "Let's face it, comedy's a dead art form. Tragedy, now that's funny."
Expand Down
49 changes: 49 additions & 0 deletions t/mojolicious/multipath_lite_app.t
@@ -0,0 +1,49 @@
use Mojo::Base -strict;

# Disable Bonjour, IPv6 and libev
BEGIN {
$ENV{MOJO_NO_BONJOUR} = $ENV{MOJO_NO_IPV6} = 1;
$ENV{MOJO_IOWATCHER} = 'Mojo::IOWatcher';
}

# "You are hereby conquered.
# Please line up in order of how much beryllium it takes to kill you."
use Test::More tests => 20;

use Mojolicious::Lite;
use Test::Mojo;

# More paths
push @{app->renderer->paths}, app->home->rel_dir('templates2');
push @{app->static->paths}, app->home->rel_dir('public2');

# GET /twenty_three
get '/twenty_three' => '23';

# GET /fourty_two
get '/fourty_two' => '42';

my $t = Test::Mojo->new;

# GET /twenty_three (templates)
$t->get_ok('/twenty_three')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Powered-By' => 'Mojolicious (Perl)')->content_is("23\n");

# GET /fourty_two (templates2)
$t->get_ok('/fourty_two')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Powered-By' => 'Mojolicious (Perl)')
->content_is("The answer is 42.\n");

# GET /hello.txt (public)
$t->get_ok('/hello.txt')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Powered-By' => 'Mojolicious (Perl)')
->content_is("Hello Mojo from a static file!\n");

# GET /hello3.txt (public2)
$t->get_ok('/hello3.txt')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Powered-By' => 'Mojolicious (Perl)')
->content_is("Hello Mojo from... ALL GLORY TO THE HYPNOTOAD!\n");
1 change: 1 addition & 0 deletions t/mojolicious/public2/hello3.txt
@@ -0,0 +1 @@
Hello Mojo from... ALL GLORY TO THE HYPNOTOAD!
3 changes: 3 additions & 0 deletions t/mojolicious/templates2/42.html.ep
@@ -0,0 +1,3 @@
%# "Oh no! Can we switch back using four or more bodies?
%# I'm not sure. I'm afraid we need to use... MATH."
The answer is <%= 40 + 2 %>.
2 changes: 1 addition & 1 deletion t/pod_coverage.t
Expand Up @@ -11,7 +11,7 @@ plan skip_all => 'set TEST_POD to enable this test (developer only!)'
my @leaf = (
qw/comment connect connection_timeout keep_alive_timeout listen/,
qw/max_redirects on_close on_error on_lock on_process on_read on_unlock/,
qw/port prepare_ioloop timeout version write x_forwarded_for/
qw/port prepare_ioloop root timeout version write x_forwarded_for/
);

# "Marge, I'm going to miss you so much. And it's not just the sex.
Expand Down

1 comment on commit 005568f

@kberov
Copy link
Contributor

@kberov kberov commented on 005568f Feb 15, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was missing this very much. Thanks!

Please sign in to comment.