Skip to content
This repository
Browse code

added exception handling to MojoX::Dispatcher::Routes and Mojo::Loader

  • Loading branch information...
commit 6b52a2832e3c6339c42927594a8ade2b40eff18e 1 parent 6ed5bab
authored June 23, 2009
4  Changes
@@ -4,10 +4,14 @@ This file documents the revision history for Perl extension Mojo.
4 4
         - Rewrote MojoX::Renderer, it is not backward compatible and
5 5
           templates need to be renamed in the following 3 part format
6 6
           "index.html.tt"!
  7
+        - Added exception support to MojoX::Dispatcher::Routes, this change
  8
+          is not backward compatible and "dispatch" calls now return
  9
+          exception objects for errors and false for success.
7 10
         - Added full HTTP 1.1 pipelining support to all Mojo layers.
8 11
         - Added layout support to MojoX::Renderer.
9 12
         - Made render call optional.
10 13
         - Added format support to MojoX::Routes.
  14
+        - Added Mojo::Loader::Exception.
11 15
         - Added wildcard symbol support to MojoX::Routes and rewrote many
12 16
           routes internals.
13 17
         - Added Makefile.PL generator.
38  lib/Mojo/Loader.pm
@@ -10,6 +10,7 @@ use base 'Mojo::Base';
10 10
 use Carp qw/carp croak/;
11 11
 use File::Basename;
12 12
 use File::Spec;
  13
+use Mojo::Loader::Exception;
13 14
 
14 15
 use constant DEBUG => $ENV{MOJO_LOADER_DEBUG} || 0;
15 16
 
@@ -52,8 +53,7 @@ sub build {
52 53
             my $instance = $module->new(@_);
53 54
             push @instances, $instance;
54 55
         };
55  
-        croak qq/Couldn't instantiate module "$module": $@/
56  
-          if $@ && $@ ne "SHORTCUT\n";
  56
+        return Mojo::Loader::Exception->new($@) if $@ && $@ ne "SHORTCUT\n";
57 57
     }
58 58
 
59 59
     return \@instances;
@@ -71,10 +71,10 @@ sub load {
71 71
 
72 72
         # Load
73 73
         eval "require $module";
74  
-        croak qq/Couldn't load module "$module": $@/ if $@;
  74
+        return Mojo::Loader::Exception->new($@) if $@;
75 75
     }
76 76
 
77  
-    return $self;
  77
+    return 0;
78 78
 }
79 79
 
80 80
 sub load_build {
@@ -84,17 +84,19 @@ sub load_build {
84 84
     $self = $self->new unless ref $self;
85 85
 
86 86
     # Load
87  
-    $self->load(shift);
  87
+    my $e = $self->load(shift);
  88
+    return $e if $e;
88 89
 
89 90
     # Build
90  
-    my $instances = $self->build(@_);
91  
-    return $instances->[0];
  91
+    $e = $self->build(@_);
  92
+    return ref $e eq 'Mojo::Loader::Exception' ? $e : $e->[0];
92 93
 }
93 94
 
94 95
 sub reload {
95 96
     while (my ($key, $file) = each %INC) {
96 97
 
97 98
         # Modified time
  99
+        next unless $file;
98 100
         my $mtime = (stat $file)[9];
99 101
 
100 102
         # Startup time as default
@@ -117,11 +119,13 @@ sub reload {
117 119
 
118 120
             # Reload
119 121
             eval { require $key };
120  
-            carp "Can't reload '$file': $@" if $@;
  122
+            return Mojo::Loader::Exception->new($@) if $@;
121 123
 
122 124
             $STATS->{$file} = $mtime;
123 125
         }
124 126
     }
  127
+
  128
+    return 0;
125 129
 }
126 130
 
127 131
 sub search {
@@ -217,21 +221,27 @@ following new ones.
217 221
 
218 222
     my $instances = $loader->build;
219 223
     my $instances = $loader->build(qw/foo bar baz/);
  224
+    my $exception = $loader->build;
  225
+    my $exception = $loader->build(qw/foo bar baz/);
220 226
 
221 227
 =head2 C<load>
222 228
 
223  
-    $loader = $loader->load;
  229
+    my $exception = $loader->load;
224 230
 
225 231
 =head2 C<load_build>
226 232
 
227  
-    my $instance = Mojo::Loader->load_build('MyApp');
228  
-    my $instance = $loader->load_build('MyApp');
229  
-    my $instance = Mojo::Loader->load_build('MyApp', qw/some args/);
230  
-    my $instance = $loader->load_build('MyApp', qw/some args/);
  233
+    my $instance  = Mojo::Loader->load_build('MyApp');
  234
+    my $instance  = $loader->load_build('MyApp');
  235
+    my $instance  = Mojo::Loader->load_build('MyApp', qw/some args/);
  236
+    my $instance  = $loader->load_build('MyApp', qw/some args/);
  237
+    my $exception = Mojo::Loader->load_build('MyApp');
  238
+    my $exception = $loader->load_build('MyApp');
  239
+    my $exception = Mojo::Loader->load_build('MyApp', qw/some args/);
  240
+    my $exception = $loader->load_build('MyApp', qw/some args/);
231 241
 
232 242
 =head2 C<reload>
233 243
 
234  
-    Mojo::Loader->reload;
  244
+    my $exception = Mojo::Loader->reload;
235 245
 
236 246
 =head2 C<search>
237 247
 
70  lib/Mojo/Loader/Exception.pm
... ...
@@ -0,0 +1,70 @@
  1
+# Copyright (C) 2008-2009, Sebastian Riedel.
  2
+
  3
+package Mojo::Loader::Exception;
  4
+
  5
+use strict;
  6
+use warnings;
  7
+
  8
+use base 'Mojo::Template::Exception';
  9
+
  10
+use IO::File;
  11
+
  12
+# You killed zombie Flanders!
  13
+# He was a zombie?
  14
+sub new {
  15
+    my $self = shift->SUPER::new();
  16
+
  17
+    # Message
  18
+    my $msg = shift;
  19
+    $self->message($msg);
  20
+
  21
+    if ($msg =~ /at\s+([^\s]+)\s+line\s+(\d+)/) {
  22
+        my $file = $1;
  23
+        my $line = $2;
  24
+
  25
+        # Context
  26
+        if (-r $file) {
  27
+
  28
+            # Slurp
  29
+            my $handle = IO::File->new("< $file");
  30
+            my @lines  = <$handle>;
  31
+
  32
+            # Line
  33
+            $self->parse_context(\@lines, $line);
  34
+        }
  35
+    }
  36
+
  37
+    return $self;
  38
+}
  39
+
  40
+1;
  41
+__END__
  42
+
  43
+=head1 NAME
  44
+
  45
+Mojo::Loader::Exception - Loader Exception
  46
+
  47
+=head1 SYNOPSIS
  48
+
  49
+    use Mojo::Loader::Exception;
  50
+    my $e = Mojo::Loader::Exception->new;
  51
+
  52
+=head1 DESCRIPTION
  53
+
  54
+L<Mojo::Loader::Exception> is a container for loader exceptions.
  55
+
  56
+=head1 ATTRIBUTES
  57
+
  58
+L<Mojo::Loader::Exception> inherits all methods from
  59
+L<Mojo::Template::Exception>.
  60
+
  61
+=head1 METHODS
  62
+
  63
+L<Mojo::Loader::Exception> inherits all methods from
  64
+L<Mojo::Template::Exception> and implements the following new ones.
  65
+
  66
+=head2 C<new>
  67
+
  68
+    my $e = Mojo::Loader::Exception->new('Something bad happened!');
  69
+
  70
+=cut
25  lib/Mojo/Scripts.pm
@@ -39,13 +39,32 @@ sub run {
39 39
     if ($script) {
40 40
         my $module =
41 41
           $self->namespace . '::' . Mojo::ByteStream->new($script)->camelize;
42  
-        Mojo::Loader->new->base($self->base)->load_build($module)->run(@args);
  42
+        my $loader = Mojo::Loader->new->base($self->base);
  43
+        my $e      = $loader->load_build($module);
  44
+
  45
+        # Exception
  46
+        if (ref $e eq 'Mojo::Loader::Exception') {
  47
+
  48
+            # Module missing
  49
+            die qq/Script "$script" missing, maybe you need to install it?\n/
  50
+              if "$e" =~ /^Can't locate /;
  51
+
  52
+            # Real error
  53
+            die $e;
  54
+        }
  55
+
  56
+        # Run
  57
+        $e->run(@args);
43 58
         return $self;
44 59
     }
45 60
 
46 61
     # Load scripts
47  
-    my $instances =
48  
-      Mojo::Loader->new($self->namespace)->base($self->base)->load->build;
  62
+    my $loader = Mojo::Loader->new($self->namespace)->base($self->base);
  63
+    my $e      = $loader->load;
  64
+    die $e if $e;
  65
+    $e = $loader->build;
  66
+    die $e if ref $e eq 'Mojo::Loader::Exception';
  67
+    my $instances = $e;
49 68
 
50 69
     # Print overview
51 70
     print $self->message;
9  lib/Mojo/Server.pm
@@ -15,7 +15,11 @@ use constant RELOAD => $ENV{MOJO_RELOAD} || 0;
15 15
 __PACKAGE__->attr(
16 16
     app => (
17 17
         chained => 1,
18  
-        default => sub { Mojo::Loader->load_build(shift->app_class) }
  18
+        default => sub {
  19
+            my $e = Mojo::Loader->load_build(shift->app_class);
  20
+            die $e if ref $e eq 'Mojo::Loader::Exception';
  21
+            return $e;
  22
+        }
19 23
     )
20 24
 );
21 25
 __PACKAGE__->attr(
@@ -33,7 +37,8 @@ __PACKAGE__->attr(
33 37
 
34 38
                 # Reload
35 39
                 if (RELOAD) {
36  
-                    Mojo::Loader->reload;
  40
+                    my $e = Mojo::Loader->reload;
  41
+                    warn $e if $e;
37 42
                     delete $self->{app};
38 43
                 }
39 44
 
24  lib/Mojo/Template.pm
@@ -303,29 +303,7 @@ sub _error {
303 303
         my $line = $1;
304 304
         my @lines = split /\n/, $self->template;
305 305
 
306  
-        # Context
307  
-        $te->line([$line, $lines[$line - 1]]);
308  
-
309  
-        # -2
310  
-        my $previous_line = $line - 3;
311  
-        my $code = $previous_line >= 0 ? $lines[$previous_line] : undef;
312  
-        push @{$te->lines_before}, [$line - 2, $code] if $code;
313  
-
314  
-        # -1
315  
-        $previous_line = $line - 2;
316  
-        $code = $previous_line >= 0 ? $lines[$previous_line] : undef;
317  
-        push @{$te->lines_before}, [$line - 1, $code] if $code;
318  
-
319  
-        # +1
320  
-        my $next_line = $line;
321  
-        $code = $next_line >= 0 ? $lines[$next_line] : undef;
322  
-        push @{$te->lines_after}, [$line + 1, $code] if $code;
323  
-
324  
-        # +2
325  
-        $next_line = $line + 1;
326  
-        $code = $next_line >= 0 ? $lines[$next_line] : undef;
327  
-        push @{$te->lines_after}, [$line + 2, $code] if $code;
328  
-
  306
+        $te->parse_context(\@lines, $line);
329 307
     }
330 308
 
331 309
     return $te;
76  lib/Mojo/Template/Exception.pm
@@ -14,6 +14,49 @@ __PACKAGE__->attr('message' => (chained => 1));
14 14
 
15 15
 # Attempted murder? Now honestly, what is that?
16 16
 # Do they give a Nobel Prize for attempted chemistry?
  17
+sub parse_context {
  18
+    my ($self, $lines, $line) = @_;
  19
+
  20
+    # Context
  21
+    my $code = $lines->[$line - 1];
  22
+    chomp $code;
  23
+    $self->line([$line, $code]);
  24
+
  25
+    # -2
  26
+    my $previous_line = $line - 3;
  27
+    $code = $previous_line >= 0 ? $lines->[$previous_line] : undef;
  28
+    if (defined $code) {
  29
+        chomp $code;
  30
+        push @{$self->lines_before}, [$line - 2, $code];
  31
+    }
  32
+
  33
+    # -1
  34
+    $previous_line = $line - 2;
  35
+    $code = $previous_line >= 0 ? $lines->[$previous_line] : undef;
  36
+    if (defined $code) {
  37
+        chomp $code;
  38
+        push @{$self->lines_before}, [$line - 1, $code];
  39
+    }
  40
+
  41
+    # +1
  42
+    my $next_line = $line;
  43
+    $code = $next_line >= 0 ? $lines->[$next_line] : undef;
  44
+    if (defined $code) {
  45
+        chomp $code;
  46
+        push @{$self->lines_after}, [$line + 1, $code];
  47
+    }
  48
+
  49
+    # +2
  50
+    $next_line = $line + 1;
  51
+    $code = $next_line >= 0 ? $lines->[$next_line] : undef;
  52
+    if (defined $code) {
  53
+        chomp $code;
  54
+        push @{$self->lines_after}, [$line + 2, $code];
  55
+    }
  56
+
  57
+    return $self;
  58
+}
  59
+
17 60
 sub to_string {
18 61
     my $self = shift;
19 62
 
@@ -38,8 +81,11 @@ sub to_string {
38 81
         $string .= $line->[0] . ': ' . $line->[1] . "\n";
39 82
     }
40 83
 
  84
+    # Delim
  85
+    $string .= "$delim\n" if length $string;
  86
+
41 87
     # Message
42  
-    $string .= ("$delim\n" . $self->message) if $self->message;
  88
+    $string .= $self->message if $self->message;
43 89
 
44 90
     return $string;
45 91
 }
@@ -54,40 +100,46 @@ Mojo::Template::Exception - Template Exception
54 100
 =head1 SYNOPSIS
55 101
 
56 102
     use Mojo::Template::Exception;
57  
-    my $te = Mojo::Template::Exception->new;
  103
+    my $e = Mojo::Template::Exception->new;
58 104
 
59 105
 =head1 DESCRIPTION
60 106
 
  107
+L<Mojo::Template::Exception> is a container for template exceptions.
  108
+
61 109
 =head1 ATTRIBUTES
62 110
 
63 111
 =head2 C<line>
64 112
 
65  
-    my $line = $te->line;
66  
-    $te      = $te->line([3, 'foo']);
  113
+    my $line = $e->line;
  114
+    $e       = $e->line([3, 'foo']);
67 115
 
68 116
 =head2 C<lines_after>
69 117
 
70  
-    my $lines = $te->lines_after;
71  
-    $te       = $te->lines_after([[1, 'bar'], [2, 'baz']]);
  118
+    my $lines = $e->lines_after;
  119
+    $e        = $e->lines_after([[1, 'bar'], [2, 'baz']]);
72 120
 
73 121
 =head2 C<lines_before>
74 122
 
75  
-    my $lines = $te->lines_before;
76  
-    $te       = $te->lines_before([[4, 'bar'], [5, 'baz']]);
  123
+    my $lines = $e->lines_before;
  124
+    $e        = $e->lines_before([[4, 'bar'], [5, 'baz']]);
77 125
 
78 126
 =head2 C<message>
79 127
 
80  
-    my $message = $te->message;
81  
-    $te         = $te->message('oops!');
  128
+    my $message = $e->message;
  129
+    $e          = $e->message('oops!');
82 130
 
83 131
 =head1 METHODS
84 132
 
85 133
 L<Mojo::Template::Exception> inherits all methods from L<Mojo::Base> and
86 134
 implements the following new ones.
87 135
 
  136
+=head2 C<parse_context>
  137
+
  138
+    $e = $e->parse_context($lines, $line);
  139
+
88 140
 =head2 C<to_string>
89 141
 
90  
-    my $string = $te->to_string;
91  
-    my $string = "$te";
  142
+    my $string = $e->to_string;
  143
+    my $string = "$e";
92 144
 
93 145
 =cut
181  lib/MojoX/Dispatcher/Routes.pm
@@ -9,6 +9,7 @@ use base 'MojoX::Routes';
9 9
 
10 10
 use Mojo::ByteStream;
11 11
 use Mojo::Loader;
  12
+use Mojo::Loader::Exception;
12 13
 
13 14
 __PACKAGE__->attr(
14 15
     disallow => (
@@ -22,15 +23,12 @@ __PACKAGE__->attr(namespace => (chained => 1));
22 23
 sub dispatch {
23 24
     my ($self, $c, $match) = @_;
24 25
 
  26
+    # Match
25 27
     $match ||= $self->match($c->tx);
26 28
     $c->match($match);
27 29
 
28  
-    # Shortcut
29  
-    return 0 unless $match;
30  
-
31  
-    # No stack, fail
32  
-    my $stack = $match->stack;
33  
-    return 0 unless @$stack;
  30
+    # No match
  31
+    return 1 unless $match && @{$match->stack};
34 32
 
35 33
     # Initialize stash with captures
36 34
     my %captures = %{$match->captures};
@@ -40,96 +38,141 @@ sub dispatch {
40 38
     }
41 39
     $c->stash({%captures});
42 40
 
  41
+    # Walk the stack
  42
+    my $e = $self->walk_stack($c);
  43
+    return $e if $e;
  44
+
  45
+    # Render
  46
+    $self->render($c);
  47
+
  48
+    # All seems ok
  49
+    return 0;
  50
+}
  51
+
  52
+sub generate_class {
  53
+    my ($self, $c, $field) = @_;
  54
+
  55
+    # Class
  56
+    my $class = $field->{class};
  57
+    my $controller = $field->{controller} || '';
  58
+    unless ($class) {
  59
+        my @class;
  60
+        for my $part (split /-/, $controller) {
  61
+
  62
+            # Junk
  63
+            next unless $part;
  64
+
  65
+            # Camelize
  66
+            push @class, Mojo::ByteStream->new($part)->camelize;
  67
+        }
  68
+        $class = join '::', @class;
  69
+    }
  70
+
  71
+    # Format
  72
+    my $namespace = $field->{namespace} || $self->namespace;
  73
+    $class = length $class ? "${namespace}::$class" : $namespace;
  74
+
  75
+    # Invalid
  76
+    return undef unless $class =~ /^[a-zA-Z0-9_:]+$/;
  77
+
  78
+    return $class;
  79
+}
  80
+
  81
+sub generate_method {
  82
+    my ($self, $c, $field) = @_;
  83
+
43 84
     # Prepare disallow
44 85
     unless ($self->{_disallow}) {
45 86
         $self->{_disallow} = {};
46 87
         $self->{_disallow}->{$_}++ for @{$self->disallow};
47 88
     }
48 89
 
  90
+    my $method = $field->{method};
  91
+    $method ||= $field->{action};
  92
+
  93
+    # Shortcut for disallowed methods
  94
+    return undef if $self->{_disallow}->{$method};
  95
+    return undef if index($method, '_') == 0;
  96
+
  97
+    # Invalid
  98
+    return undef unless $method =~ /^[a-zA-Z0-9_:]+$/;
  99
+
  100
+    return $method;
  101
+}
  102
+
  103
+sub render {
  104
+    my ($self, $c) = @_;
  105
+
  106
+    # Render
  107
+    $c->render unless $c->stash->{rendered} || $c->res->code;
  108
+}
  109
+
  110
+sub walk_stack {
  111
+    my ($self, $c) = @_;
  112
+
49 113
     # Walk the stack
50  
-    for my $field (@$stack) {
  114
+    for my $field (@{$c->match->stack}) {
51 115
 
52 116
         # Don't cache errors
53 117
         local $@;
54 118
 
55 119
         # Method
56  
-        my $method = $field->{method};
57  
-        $method ||= $field->{action};
58  
-
59  
-        # Shortcut for disallowed methods
60  
-        next if $self->{_disallow}->{$method};
61  
-        next if index($method, '_') == 0;
  120
+        my $method = $self->generate_method($c, $field);
  121
+        next unless $method;
62 122
 
63 123
         # Class
64  
-        my $class = $field->{class};
65  
-        my $controller = $field->{controller} || '';
66  
-        unless ($class) {
67  
-            my @class;
68  
-            for my $part (split /-/, $controller) {
69  
-
70  
-                # Junk
71  
-                next unless $part;
72  
-
73  
-                # Camelize
74  
-                push @class, Mojo::ByteStream->new($part)->camelize;
75  
-            }
76  
-            $class = join '::', @class;
77  
-        }
78  
-
79  
-        # Format
80  
-        my $namespace = $field->{namespace} || $self->namespace;
81  
-        $class = length $class ? "${namespace}::$class" : $namespace;
  124
+        my $class = $self->generate_class($c, $field);
  125
+        next unless $class;
82 126
 
83 127
         # Debug
84  
-        $c->app->log->debug(
85  
-            qq/Dispatching "$method" in "$controller($class)"/);
86  
-
87  
-        # Shortcut for invalid class and method
88  
-        next
89  
-          unless $class =~ /^[a-zA-Z0-9_:]+$/
90  
-              && $method =~ /^[a-zA-Z0-9_]+$/;
  128
+        $c->app->log->debug(qq/Dispatching "${class}::$method"./);
91 129
 
92 130
         # Captures
93 131
         $c->match->captures($field);
94 132
 
95  
-        # Load
  133
+        # Load class
96 134
         $self->{_loaded} ||= {};
97  
-        eval {
98  
-            Mojo::Loader->new->load($class);
99  
-            $self->{_loaded}->{$class}++;
100  
-        } unless $self->{_loaded}->{$class};
  135
+        my $e = 0;
  136
+        unless ($self->{_loaded}->{$class}) {
  137
+            $e = Mojo::Loader->new->load($class);
  138
+            $self->{_loaded}->{$class}++ unless $e;
  139
+        }
101 140
 
102 141
         # Load error
103  
-        if ($@) {
104  
-            $c->app->log->debug(
105  
-                qq/Couldn't load controller class "$class":\n$@/);
106  
-            return 0;
  142
+        if ($e) {
  143
+            $c->app->log->debug($e);
  144
+            return $e;
107 145
         }
108 146
 
109  
-        # Dispatch
110  
-        my $done;
  147
+        # Check class
111 148
         eval {
112  
-            die "$class is not a controller"
  149
+            die
113 150
               unless $class->isa('MojoX::Dispatcher::Routes::Controller');
114  
-            $done = $class->new(ctx => $c)->$method($c);
115 151
         };
116 152
 
  153
+        # Not a conroller
  154
+        if ($@) {
  155
+            $c->app->log->debug(qq/"$class" is not a controller./);
  156
+            return 1;
  157
+        }
  158
+
  159
+        # Dispatch
  160
+        my $done;
  161
+        eval { $done = $class->new(ctx => $c)->$method($c) };
  162
+
117 163
         # Controller error
118 164
         if ($@) {
119  
-            $c->app->log->debug(
120  
-                qq/Controller error in "${class}::$method":\n$@/);
121  
-            return 0;
  165
+            my $e = Mojo::Loader::Exception->new($@);
  166
+            $c->app->log->debug($e);
  167
+            return $e;
122 168
         }
123 169
 
124 170
         # Break the chain
125 171
         last unless $done;
126 172
     }
127 173
 
128  
-    # Render
129  
-    $c->render unless $c->stash->{rendered} || $c->res->code;
130  
-
131  
-    # All seems ok
132  
-    return 1;
  174
+    # Done
  175
+    return 0;
133 176
 }
134 177
 
135 178
 1;
@@ -173,12 +216,28 @@ implements the follwing the ones.
173 216
 
174 217
 =head2 C<dispatch>
175 218
 
176  
-    my $success = $dispatcher->dispatch(
  219
+    my $exception = $dispatcher->dispatch(
177 220
         MojoX::Dispatcher::Routes::Context->new
178 221
     );
179  
-    my $success = $dispatcher->dispatch(
  222
+    my $exception = $dispatcher->dispatch(
180 223
         MojoX::Dispatcher::Routes::Context->new,
181 224
         MojoX::Routes::Match->new
182 225
     );
183 226
 
  227
+=head2 C<generate_class>
  228
+
  229
+    my $class = $dispatcher->generate_class($c, $field);
  230
+
  231
+=head2 C<generate_method>
  232
+
  233
+    my $method = $dispatcher->genrate_method($c, $field);
  234
+
  235
+=head2 C<render>
  236
+
  237
+    $dispatcher->render($c);
  238
+
  239
+=head2 C<walk_stack>
  240
+
  241
+    my $exception = $dispatcher->walk_stack($c);
  242
+
184 243
 =cut
36  lib/MojoX/Dispatcher/Static.pm
@@ -28,14 +28,14 @@ sub dispatch {
28 28
 
29 29
     # Prefix
30 30
     if (my $prefix = $self->prefix) {
31  
-        return 0 unless $c->req->url->path =~ /^$prefix.*/;
  31
+        return 1 unless $c->req->url->path =~ /^$prefix.*/;
32 32
     }
33 33
 
34 34
     # Path
35 35
     my @parts = @{$c->req->url->path->clone->canonicalize->parts};
36 36
 
37 37
     # Shortcut
38  
-    return 0 unless @parts;
  38
+    return 1 unless @parts;
39 39
 
40 40
     # Serve static file
41 41
     return $self->serve($c, File::Spec->catfile(@parts));
@@ -58,7 +58,7 @@ sub serve {
58 58
     if (-f $path) {
59 59
 
60 60
         # Log
61  
-        $c->app->log->debug(qq/Serving static file "$path"/);
  61
+        $c->app->log->debug(qq/Serving static file "$path"./);
62 62
 
63 63
         my $res = $c->res;
64 64
         if (-r $path) {
@@ -72,13 +72,13 @@ sub serve {
72 72
                 if (Mojo::Date->new($date)->epoch == $stat->mtime) {
73 73
 
74 74
                     # Log
75  
-                    $c->app->log->debug('File not modified');
  75
+                    $c->app->log->debug('File not modified.');
76 76
 
77 77
                     $res->code(304);
78 78
                     $res->headers->remove('Content-Type');
79 79
                     $res->headers->remove('Content-Length');
80 80
                     $res->headers->remove('Content-Disposition');
81  
-                    return 1;
  81
+                    return 0;
82 82
                 }
83 83
             }
84 84
 
@@ -91,21 +91,21 @@ sub serve {
91 91
 
92 92
             $res->headers->content_type($type);
93 93
             $res->content->file->path($path);
94  
-            return 1;
  94
+            return 0;
95 95
         }
96 96
 
97 97
         # Exists, but is forbidden
98 98
         else {
99 99
 
100 100
             # Log
101  
-            $c->app->log->debug('File forbidden');
  101
+            $c->app->log->debug('File forbidden.');
102 102
 
103 103
             $res->code(403);
104  
-            return 1;
  104
+            return 0;
105 105
         }
106 106
     }
107 107
 
108  
-    return 0;
  108
+    return 1;
109 109
 }
110 110
 
111 111
 sub serve_404 { shift->serve_error(shift, 404) }
@@ -116,7 +116,7 @@ sub serve_error {
116 116
     my ($self, $c, $code, $path) = @_;
117 117
 
118 118
     # Shortcut
119  
-    return 0 unless $c && $code;
  119
+    return 1 unless $c && $code;
120 120
 
121 121
     my $res = $c->res;
122 122
 
@@ -133,7 +133,7 @@ sub serve_error {
133 133
     if (-r $path) {
134 134
 
135 135
         # Log
136  
-        $c->app->log->debug(qq/Serving error file "$path"/);
  136
+        $c->app->log->debug(qq/Serving error file "$path"./);
137 137
 
138 138
         # File
139 139
         $res->content(Mojo::Content->new(file => Mojo::File->new));
@@ -152,7 +152,7 @@ sub serve_error {
152 152
     elsif ($code == 404) {
153 153
 
154 154
         # Log
155  
-        $c->app->log->debug('Serving 404 error');
  155
+        $c->app->log->debug('Serving 404 error.');
156 156
 
157 157
         $res->headers->content_type('text/html');
158 158
         $res->body(<<'EOF');
@@ -169,7 +169,7 @@ EOF
169 169
     else {
170 170
 
171 171
         # Log
172  
-        $c->app->log->debug(qq/Serving error "$code"/);
  172
+        $c->app->log->debug(qq/Serving error "$code"./);
173 173
 
174 174
         $res->headers->content_type('text/html');
175 175
         $res->body(<<'EOF');
@@ -182,7 +182,7 @@ EOF
182 182
 EOF
183 183
     }
184 184
 
185  
-    return 1;
  185
+    return 0;
186 186
 }
187 187
 
188 188
 1;
@@ -245,18 +245,18 @@ implements the follwing the ones.
245 245
 
246 246
     my $success = $dispatcher->dispatch($c);
247 247
 
248  
-Returns true if a file matching the request could be found and a response be
  248
+Returns false if a file matching the request could be found and a response be
249 249
 prepared.
250  
-Returns false otherwise.
  250
+Returns true otherwise.
251 251
 Expects a L<MojoX::Context> object as first argument.
252 252
 
253 253
 =head2 C<serve>
254 254
 
255 255
     my $success = $dispatcher->serve($c, 'foo/bar.html');
256 256
 
257  
-Returns true if a readable file could be found under C<root> and a response
  257
+Returns false if a readable file could be found under C<root> and a response
258 258
 be prepared.
259  
-Returns false otherwise.
  259
+Returns true otherwise.
260 260
 Expects a L<MojoX::Context> object and a path as arguments.
261 261
 If no type can be determined, C<text/plain> will be used.
262 262
 A C<Last-Modified> header will always be set according to the last modified
8  lib/MojoX/Renderer.pm
@@ -85,7 +85,7 @@ sub render {
85 85
 
86 86
     # Debug
87 87
     unless ($r) {
88  
-        $c->app->log->debug(qq/No handler for "$handler" available/);
  88
+        $c->app->log->debug(qq/No handler for "$handler" available./);
89 89
         return undef;
90 90
     }
91 91
 
@@ -134,7 +134,7 @@ sub _fix_format {
134 134
 
135 135
     # Missing format
136 136
     else {
137  
-        $c->app->log->debug('Template format missing');
  137
+        $c->app->log->debug('Template format missing.');
138 138
         return undef;
139 139
     }
140 140
 
@@ -162,14 +162,14 @@ sub _fix_handler {
162 162
             if (-f $p) {
163 163
                 $found++;
164 164
                 $path = $p;
165  
-                $c->app->log->debug(qq/Template found "$path"/);
  165
+                $c->app->log->debug(qq/Template found "$path"./);
166 166
                 last;
167 167
             }
168 168
         }
169 169
 
170 170
         # Nothing found
171 171
         unless ($found) {
172  
-            $c->app->log->debug(qq/Template not found "$path.*"/);
  172
+            $c->app->log->debug(qq/Template not found "$path.*"./);
173 173
             return undef;
174 174
         }
175 175
     }
2  lib/MojoX/Routes/Pattern.pm
@@ -13,7 +13,7 @@ __PACKAGE__->attr(defaults => (chained => 1, default => sub { {} }));
13 13
 __PACKAGE__->attr([qw/format pattern regex/] => (chained => 1));
14 14
 __PACKAGE__->attr(quote_end      => (chained => 1, default => ')'));
15 15
 __PACKAGE__->attr(quote_start    => (chained => 1, default => '('));
16  
-__PACKAGE__->attr(relaxed_start  => (chained => 1, default => '^'));
  16
+__PACKAGE__->attr(relaxed_start  => (chained => 1, default => '+'));
17 17
 __PACKAGE__->attr(reqs           => (chained => 1, default => sub { {} }));
18 18
 __PACKAGE__->attr(symbol_start   => (chained => 1, default => ':'));
19 19
 __PACKAGE__->attr(symbols        => (chained => 1, default => sub { [] }));
28  lib/Mojolicious.pm
@@ -8,8 +8,8 @@ use warnings;
8 8
 use base 'Mojo';
9 9
 
10 10
 use Mojo::Loader;
11  
-use Mojolicious::Dispatcher;
12 11
 use Mojolicious::Renderer;
  12
+use MojoX::Dispatcher::Routes;
13 13
 use MojoX::Dispatcher::Static;
14 14
 use MojoX::Types;
15 15
 
@@ -34,7 +34,7 @@ __PACKAGE__->attr(
34 34
 __PACKAGE__->attr(
35 35
     routes => (
36 36
         chained => 1,
37  
-        default => sub { Mojolicious::Dispatcher->new }
  37
+        default => sub { MojoX::Dispatcher::Routes->new }
38 38
     )
39 39
 );
40 40
 __PACKAGE__->attr(
@@ -94,14 +94,32 @@ sub build_ctx {
94 94
 sub dispatch {
95 95
     my ($self, $c) = @_;
96 96
 
  97
+    # New request
  98
+    my $path = $c->req->url->path;
  99
+    $self->log->debug(qq/*** Request for "$path". ***/);
  100
+
97 101
     # Try to find a static file
98  
-    my $done = $self->static->dispatch($c);
  102
+    my $e = $self->static->dispatch($c);
99 103
 
100 104
     # Use routes if we don't have a response yet
101  
-    $done ||= $self->routes->dispatch($c);
  105
+    $e = $self->routes->dispatch($c) if $e;
  106
+
  107
+    # Exception
  108
+    if (ref $e) {
  109
+
  110
+        # Development mode
  111
+        if ($self->mode eq 'development') {
  112
+            $c->stash(exception => $e);
  113
+            $c->res->code(500);
  114
+            $c->render(template => 'exception');
  115
+        }
  116
+
  117
+        # Production mode
  118
+        else { $self->static->serve_500($c) }
  119
+    }
102 120
 
103 121
     # Nothing found
104  
-    $self->static->serve_404($c) unless $done;
  122
+    elsif ($e) { $self->static->serve_404($c) }
105 123
 }
106 124
 
107 125
 # Bite my shiny metal ass!
73  lib/Mojolicious/Dispatcher.pm
... ...
@@ -1,73 +0,0 @@
1  
-# Copyright (C) 2008-2009, Sebastian Riedel.
2  
-
3  
-package Mojolicious::Dispatcher;
4  
-
5  
-use strict;
6  
-use warnings;
7  
-
8  
-use base 'MojoX::Dispatcher::Routes';
9  
-
10  
-__PACKAGE__->attr([qw/method user_agent/] => (chained => 1));
11  
-
12  
-# That's not why people watch TV.
13  
-# Clever things make people feel stupid and unexpected things make them feel
14  
-# scared.
15  
-sub match {
16  
-    my ($self, $match) = @_;
17  
-
18  
-    # Method
19  
-    if (my $regex = $self->method) {
20  
-        return undef unless $match->tx->req->method =~ /$regex/;
21  
-    }
22  
-
23  
-    # User-Agent header
24  
-    if (my $regex = $self->user_agent) {
25  
-        my $ua = $match->tx->req->headers->user_agent || '';
26  
-        return undef unless $ua =~ /$regex/;
27  
-    }
28  
-
29  
-    return $self->SUPER::match($match);
30  
-}
31  
-
32  
-1;
33  
-__END__
34  
-
35  
-=head1 NAME
36  
-
37  
-Mojolicious::Dispatcher - Dispatcher
38  
-
39  
-=head1 SYNOPSIS
40  
-
41  
-    use Mojolicious::Dispatcher;
42  
-
43  
-    my $routes = Mojolicious::Dispatcher->new;
44  
-
45  
-=head1 DESCRIPTION
46  
-
47  
-L<Mojolicous::Dispatcher> is the default L<Mojolicious> dispatcher.
48  
-
49  
-=head1 ATTRIBUTES
50  
-
51  
-L<Mojolicious::Dispatcher> inherits all attributes from
52  
-L<MojoX::Dispatcher::Routes> and implements the following new ones.
53  
-
54  
-=head2 C<method>
55  
-
56  
-    my $method  = $dispatcher->method;
57  
-    $dispatcher = $dispatcher->method(qr/GET|POST/);
58  
-
59  
-=head2 C<user_agent>
60  
-
61  
-    my $ua      = $dispatcher->user_agent;
62  
-    $dispatcher = $dispatcher->user_agent(qr/GET|POST/);
63  
-
64  
-=head1 METHODS
65  
-
66  
-L<Mojolicious::Dispatcher> inherits all methods from
67  
-L<MojoX::Dispatcher::Routes> and implements the following new ones.
68  
-
69  
-=head2 C<match>
70  
-
71  
-    my $match = $routes->match($tx);
72  
-
73  
-=cut
43  lib/Mojolicious/Script/Generate/App.pm
@@ -46,12 +46,15 @@ sub run {
46 46
 
47 47
     # Static
48 48
     $self->render_to_rel_file('404',    "$name/public/404.html");
  49
+    $self->render_to_rel_file('500',    "$name/public/500.html");
49 50
     $self->render_to_rel_file('static', "$name/public/index.html");
50 51
 
51  
-    # Layout and Template
  52
+    # Layout and Templates
52 53
     $self->renderer->line_start('%%');
53 54
     $self->renderer->tag_start('<%%');
54 55
     $self->renderer->tag_end('%%>');
  56
+    $self->render_to_rel_file('exception',
  57
+        "$name/templates/exception.html.epl");
55 58
     $self->render_to_rel_file('layout',
56 59
         "$name/templates/layouts/default.html.epl");
57 60
     $self->render_to_rel_file('welcome',
@@ -97,6 +100,11 @@ __404__
97 100
     <head><title>Document not found.</title></head>
98 101
     <body><h2>Document not found.</h2></body>
99 102
 </html>
  103
+__500__
  104
+<!doctype html>
  105
+    <head><title>Internal server error.</title></head>
  106
+    <body><h2>Internal server error.</h2></body>
  107
+</html>
100 108
 __mojo__
101 109
 % my $class = shift;
102 110
 #!/usr/bin/perl
@@ -139,20 +147,6 @@ use base 'Mojolicious';
139 147
 
140 148
 our $VERSION = '0.1';
141 149
 
142  
-# This method will run for each request
143  
-sub dispatch {
144  
-    my ($self, $c) = @_;
145  
-
146  
-    # Try to find a static file
147  
-    my $done = $self->static->dispatch($c);
148  
-
149  
-    # Use routes if we don't have a response yet
150  
-    $done ||= $self->routes->dispatch($c);
151  
-
152  
-    # Nothing found, serve static file "public/404.html"
153  
-    $self->static->serve_404($c) unless $done;
154  
-}
155  
-
156 150
 # This method will run once at server start
157 151
 sub startup {
158 152
     my $self = shift;
@@ -234,6 +228,25 @@ $client->process_local('<%= $class %>', $tx);
234 228
 is($tx->res->code, 200);
235 229
 is($tx->res->headers->content_type, 'text/html');
236 230
 like($tx->res->content->file->slurp, qr/Mojolicious Web Framework/i);
  231
+__exception__
  232
+% my $self = shift;
  233
+% my $e = $self->stash('exception');
  234
+This page was generated from the template
  235
+"templates/exception.html.epl".
  236
+<pre><%= $e->message %></pre>
  237
+<pre>
  238
+% for my $line (@{$e->lines_before}) {
  239
+    <%= $line->[0] %>: <%= $line->[1] %>
  240
+% }
  241
+% if ($e->line->[0]) {
  242
+    <%= $e->line->[0] %>: <%= $e->line->[1] %>
  243
+% }
  244
+% for my $line (@{$e->lines_after}) {
  245
+    <%= $line->[0] %>: <%= $line->[1] %>
  246
+% }
  247
+</pre>
  248
+% use Data::Dumper;
  249
+<pre><%= Dumper $self->stash %></pre>
237 250
 __layout__
238 251
 % my $self = shift;
239 252
 <!doctype html>
15  t/mojo/lib/LoaderException.pm
... ...
@@ -0,0 +1,15 @@
  1
+# Copyright (C) 2008-2009, Sebastian Riedel.
  2
+
  3
+package LoaderException;
  4
+
  5
+use warnings;
  6
+use strict;
  7
+
  8
+use base 'Mojo::Base';
  9
+
  10
+# No offence Apu, but when they’re handing out religions you must be out
  11
+# taking a whizz.
  12
+
  13
+foo {
  14
+
  15
+1;
26  t/mojo/loader.t
@@ -11,7 +11,7 @@ use Test::More;
11 11
 if ($INC{'Devel/Cover.pm'}) {
12 12
     plan skip_all => "Loader tests don't play nice with Devel::Cover";
13 13
 }
14  
-else { plan tests => 12 }
  14
+else { plan tests => 21 }
15 15
 
16 16
 use FindBin;
17 17
 use lib "$FindBin::Bin/lib";
@@ -24,7 +24,29 @@ use IO::File;
24 24
 # Ow. OW. Oh, they're defending themselves somehow.
25 25
 use_ok('Mojo::Loader');
26 26
 
27  
-my $loader  = Mojo::Loader->new;
  27
+# Exception
  28
+my $loader = Mojo::Loader->new;
  29
+my $e      = $loader->load('LoaderException');
  30
+is(ref $e, 'Mojo::Loader::Exception');
  31
+like($e->message, qr/Missing right curly/);
  32
+is($e->lines_before->[0]->[0], 13);
  33
+is($e->lines_before->[0]->[1], 'foo {');
  34
+is($e->lines_before->[1]->[0], 14);
  35
+is($e->lines_before->[1]->[1], '');
  36
+is($e->line->[0],              15);
  37
+is($e->line->[1],              "1;");
  38
+$e->message("oops!\n");
  39
+is("$e", <<'EOF');
  40
+Error around line 15.
  41
+----------------------------------------------------------------------------
  42
+13: foo {
  43
+14: 
  44
+15: 1;
  45
+----------------------------------------------------------------------------
  46
+oops!
  47
+EOF
  48
+
  49
+$loader = Mojo::Loader->new;
28 50
 my $modules = $loader->search('LoaderTest')->modules;
29 51
 my @modules = sort @$modules;
30 52
 
10  t/mojolicious/app.t
@@ -5,7 +5,7 @@
5 5
 use strict;
6 6
 use warnings;
7 7
 
8  
-use Test::More tests => 33;
  8
+use Test::More tests => 35;
9 9
 
10 10
 use FindBin;
11 11
 use lib "$FindBin::Bin/lib";
@@ -22,8 +22,14 @@ use_ok('MojoliciousTest');
22 22
 
23 23
 my $client = Mojo::Client->new;
24 24
 
  25
+# SyntaxError::foo
  26
+my $tx = Mojo::Transaction->new_get('/syntax_error/foo');
  27
+$client->process_local('MojoliciousTest', $tx);
  28
+is($tx->res->code, 500);
  29
+like($tx->res->body, qr/Missing right curly/);
  30
+
25 31
 # Foo::test
26  
-my $tx = Mojo::Transaction->new_get('/foo/test', 'X-Test' => 'Hi there!');
  32
+$tx = Mojo::Transaction->new_get('/foo/test', 'X-Test' => 'Hi there!');
27 33
 $client->process_local('MojoliciousTest', $tx);
28 34
 is($tx->res->code,                        200);
29 35
 is($tx->res->headers->header('X-Bender'), 'Kiss my shiny metal ass!');
14  t/mojolicious/lib/MojoliciousTest/SyntaxError.pm
... ...
@@ -0,0 +1,14 @@
  1
+# Copyright (C) 2008-2009, Sebastian Riedel.
  2
+
  3
+package MojoliciousTest::SyntaxError;
  4
+
  5
+use strict;
  6
+use warnings;
  7
+