The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 010
META.json 22
META.yml 22
examples/websocket.pl 22
lib/Mojo/Content/Single.pm 33
lib/Mojo/Headers.pm 12
lib/Mojo/Message/Request.pm 23
lib/Mojo/Message/Response.pm 22
lib/Mojo/Server/Daemon.pm 11
lib/Mojo/Server/PSGI.pm 11
lib/Mojo/UserAgent/Transactor.pm 55
lib/Mojo/Util.pm 13
lib/Mojolicious/Command/cpanify.pm 33
lib/Mojolicious/Guides/Contributing.pod 12
lib/Mojolicious/Guides/Cookbook.pod 22
lib/Mojolicious/Guides/FAQ.pod 09
lib/Mojolicious/Guides/Rendering.pod 44
lib/Mojolicious/Guides/Routing.pod 22
lib/Mojolicious/Guides.pod 04
lib/Mojolicious/Lite.pm 39
lib/Mojolicious/Plugin/HeaderCondition.pm 32
lib/Mojolicious/Plugin/TagHelpers.pm 81
lib/Mojolicious/Renderer.pm 01
lib/Mojolicious/Static.pm 23
lib/Mojolicious/templates/development.html.ep 1313
lib/Mojolicious/templates/exception.html.ep 22
lib/Mojolicious/templates/mojobar.html.ep 44
lib/Mojolicious/templates/not_found.html.ep 22
lib/Mojolicious/templates/perldoc.html.ep 22
lib/Mojolicious.pm 23
t/mojolicious/app.t 12
t/mojolicious/dispatcher_lite_app.t 12
t/mojolicious/exception_lite_app.t 11
t/mojolicious/lite_app.t 12
34 files changed (This is a version diff) 79111
@@ -1,4 +1,14 @@
 
+4.93  2014-04-13
+  - Fixed bug where Mojolicious::Static would not use the correct default MIME
+    type.
+
+4.92  2014-04-08
+  - Removed deprecated use of hash references for optgroup generation with
+    select_field helper.
+  - Improved dumper helper to escape unprintable characters.
+  - Fixed small handler detection bug in Mojolicious::Renderer.
+
 4.91  2014-03-29
   - Added daemonize method to Mojo::Server.
   - Added ensure_pid_file method to Mojo::Server::Prefork.
@@ -4,7 +4,7 @@
       "Sebastian Riedel <sri@cpan.org>"
    ],
    "dynamic_config" : 1,
-   "generated_by" : "ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.140640",
+   "generated_by" : "ExtUtils::MakeMaker version 6.96, CPAN::Meta::Converter version 2.140640",
    "license" : [
       "artistic_2"
    ],
@@ -51,5 +51,5 @@
       },
       "x_MailingList" : "http://groups.google.com/group/mojolicious"
    },
-   "version" : "4.91"
+   "version" : "4.93"
 }
@@ -7,7 +7,7 @@ build_requires:
 configure_requires:
   ExtUtils::MakeMaker: '0'
 dynamic_config: 1
-generated_by: 'ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.140640'
+generated_by: 'ExtUtils::MakeMaker version 6.96, CPAN::Meta::Converter version 2.140640'
 license: artistic_2
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -26,4 +26,4 @@ resources:
   homepage: http://mojolicio.us
   license: http://www.opensource.org/licenses/artistic-license-2.0
   repository: http://github.com/kraih/mojo
-version: '4.91'
+version: '4.93'
@@ -24,7 +24,7 @@ __DATA__
 <html>
   <head>
     <title>WebSocket Test</title>
-    %= javascript begin
+    <script>
       var ws;
       if ("WebSocket" in window) {
         ws = new WebSocket('<%= url_for('test')->to_abs %>');
@@ -40,7 +40,7 @@ __DATA__
       else {
         document.body.innerHTML += 'Browser does not support WebSockets.';
       }
-    % end
+    </script>
   </head>
   <body>Testing WebSockets: </body>
 </html>
@@ -107,11 +107,11 @@ C<auto_upgrade> enabled.
 
 =head2 auto_upgrade
 
-  my $upgrade = $single->auto_upgrade;
-  $single     = $single->auto_upgrade(0);
+  my $bool = $single->auto_upgrade;
+  $single  = $single->auto_upgrade($bool);
 
 Try to detect multipart content and automatically upgrade to a
-L<Mojo::Content::MultiPart> object, defaults to C<1>.
+L<Mojo::Content::MultiPart> object, defaults to a true value.
 
 =head1 METHODS
 
@@ -48,7 +48,8 @@ sub from_hash {
   delete $self->{headers} if keys %{$hash} == 0;
 
   # Merge
-  while (my ($header, $value) = each %$hash) {
+  for my $header (keys %$hash) {
+    my $value = $hash->{$header};
     $self->add($header => ref $value eq 'ARRAY' ? @$value : $value);
   }
 
@@ -14,7 +14,7 @@ my $START_LINE_RE = qr/
   ([a-zA-Z]+)                                            # Method
   \s+
   ([0-9a-zA-Z!#\$\%&'()*+,\-.\/:;=?\@[\\\]^_`\{|\}~]+)   # URL
-  (?:\s+HTTP\/(\d\.\d))?                                 # Version
+  \s+HTTP\/(\d\.\d)                                      # Version
   $
 /x;
 
@@ -196,7 +196,8 @@ sub _parse_env {
   my $headers = $self->headers;
   my $url     = $self->url;
   my $base    = $url->base;
-  while (my ($name, $value) = each %$env) {
+  for my $name (keys %$env) {
+    my $value = $env->{$name};
     next unless $name =~ s/^HTTP_//i;
     $name =~ y/_/-/;
     $headers->header($name => $value);
@@ -88,7 +88,7 @@ sub cookies {
   return $self;
 }
 
-sub default_message { $MESSAGES{$_[1] || $_[0]->code || 404} || '' }
+sub default_message { $MESSAGES{$_[1] || $_[0]->code // 404} || '' }
 
 sub extract_start_line {
   my ($self, $bufref) = @_;
@@ -128,7 +128,7 @@ sub get_start_line_chunk {
 sub is_empty {
   my $self = shift;
   return undef unless my $code = $self->code;
-  return $self->is_status_class(100) || $code eq 204 || $code eq 304;
+  return $self->is_status_class(100) || $code == 204 || $code == 304;
 }
 
 sub is_status_class {
@@ -113,7 +113,7 @@ sub _finish {
   if (my $ws = $c->{tx} = delete $c->{ws}) {
 
     # Successful upgrade
-    if ($ws->res->code eq '101') {
+    if ($ws->res->code == 101) {
       weaken $self;
       $ws->on(resume => sub { $self->_write($id) });
     }
@@ -30,7 +30,7 @@ sub run {
 
   # PSGI response
   my $io = Mojo::Server::PSGI::_IO->new(tx => $tx, empty => $tx->is_empty);
-  return [$res->code || 404, \@headers, $io];
+  return [$res->code // 404, \@headers, $io];
 }
 
 sub to_psgi_app {
@@ -70,8 +70,8 @@ sub redirect {
 
   # Commonly used codes
   my $res = $old->res;
-  my $code = $res->code // '';
-  return undef unless grep { $_ eq $code } 301, 302, 303, 307, 308;
+  my $code = $res->code // 0;
+  return undef unless grep { $_ == $code } 301, 302, 303, 307, 308;
 
   # Fix broken location without authority and/or scheme
   return unless my $location = $res->headers->location;
@@ -81,7 +81,7 @@ sub redirect {
   # Clone request if necessary
   my $new = Mojo::Transaction::HTTP->new;
   my $req = $old->req;
-  if ($code eq 307 || $code eq 308) {
+  if ($code == 307 || $code == 308) {
     return undef unless my $clone = $req->clone;
     $new->req($clone);
   }
@@ -126,8 +126,8 @@ sub tx {
 
 sub upgrade {
   my ($self, $tx) = @_;
-  my $code = $tx->res->code // '';
-  return undef unless $tx->req->is_handshake && $code eq '101';
+  my $code = $tx->res->code // 0;
+  return undef unless $tx->req->is_handshake && $code == 101;
   my $ws = Mojo::Transaction::WebSocket->new(handshake => $tx, masked => 1);
   return $ws->client_challenge ? $ws : undef;
 }
@@ -111,7 +111,9 @@ sub deprecated {
   $ENV{MOJO_FATAL_DEPRECATIONS} ? croak(@_) : carp(@_);
 }
 
-sub dumper { Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Dump }
+sub dumper {
+  Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump;
+}
 
 sub encode { _encoding($_[0])->encode("$_[1]") }
 
@@ -28,10 +28,10 @@ sub run {
   );
 
   unless ($tx->success) {
-    my $code = $tx->res->code || '';
+    my $code = $tx->res->code // 0;
     my $msg = $tx->error;
-    if    ($code eq '401') { $msg = 'Wrong username or password.' }
-    elsif ($code eq '409') { $msg = 'File already exists on CPAN.' }
+    if    ($code == 401) { $msg = 'Wrong username or password.' }
+    elsif ($code == 409) { $msg = 'File already exists on CPAN.' }
     die qq{Problem uploading file "$file". ($msg)\n};
   }
 
@@ -166,7 +166,8 @@ consistent if not.
 The master source code repository should always be kept in a stable state, use
 feature branches for actual development.
 
-Code has to be run through L<Perl::Tidy> with the included C<.perltidyrc>, and
+Code has to be run through L<Perl::Tidy> with the included
+L<.perltidyrc|https://github.com/kraih/mojo/blob/master/.perltidyrc>, and
 everything should look like it was written by a single person.
 
 Functions and methods should be as short as possible, no spaghetti code.
@@ -1220,8 +1220,8 @@ that you can use or overload.
   $ ./myapp.pl spy secrets
   secr3t
 
-And to make your commands application specific, just put them in a different
-namespace.
+And to make your commands application specific, just add a custom namespace to
+L<Mojolicious::Commands/"namespaces">.
 
   # Application
   package MyApp;
@@ -71,6 +71,15 @@ In addition we will also keep the distribution installable up to a certain
 legacy version that we deem worthy of supporting, but not specifically
 optimize for it, this is currently 5.10.1.
 
+=head2 Do I need to clean my environment before testing Mojolicious?
+
+Mojolicious uses many environment variables both internally and externally,
+notably (but not exclusively) those starting with the prefix C<MOJO_*>. The 
+test suite expects a clean environment; testing with a non-standard
+environment is unsupported and is unlikely to succeed. Therefore when
+installing or upgrading Mojolicious and when running its tests, we highly
+recommend using an environment which does not set these variables.
+
 =head2 What is the difference between blocking and non-blocking operations?
 
 A I<blocking> operation is a subroutine that blocks the execution of the
@@ -309,8 +309,8 @@ Since everything is just Perl normal control structures just work.
     <%= $framework %> was written by <%= $author %>.
   % }
 
-  % while (my ($app, $description) = each %$examples) {
-    <%= $app %> is a <%= $description %>.
+  % if (my $description = $examples->{tweetylicious}) {
+    Tweetylicious is a <%= $description %>.
   % }
 
 =head2 Content negotiation
@@ -705,10 +705,10 @@ with methods like L<Mojolicious::Validator::Validation/"is_valid">.
   <!DOCTYPE html>
   <html>
     <head>
-      %= stylesheet begin
+      <style>
         label.field-with-error { color: #dd7e5e }
         input.field-with-error { background-color: #fd9e7e }
-      % end
+      </style>
     </head>
     <body>
       %= form_for index => begin
@@ -610,8 +610,8 @@ operations to finish before reaching the next dispatch cycle.
 
 From the tutorial you should already know L<Mojolicious::Lite> routes, which
 are in fact just a small convenience layer around everything described above
-and accessible through methods like L<Mojolicious::Routes::Route/"get"> as
-part of the normal router.
+and accessible through methods like L<Mojolicious::Routes::Route/"get"> and
+L<Mojolicious::Routes::Route/"any"> as part of the normal router.
 
   # POST /foo -> {controller => 'foo', action => 'abc'}
   $r->post('/foo')->to(controller => 'foo', action => 'abc');
@@ -133,6 +133,10 @@ designed to be used with it and are being developed under the same umbrella.
 
 Pure-Perl non-blocking I/O MongoDB driver.
 
+=item L<Minion>
+
+Job queue.
+
 =back
 
 =head1 REFERENCE
@@ -254,6 +254,11 @@ in debugging your application.
 
   app->start;
 
+You can even use CSS selectors with L<Mojolicious::Command::get> to extract
+only the information you're actually interested in.
+
+  $ ./myapp.pl get /dies '#error'
+
 =head2 Route names
 
 All routes can have a name associated with them, this allows automatic
@@ -725,7 +730,8 @@ Both have a higher precedence than routes.
 =head2 External templates
 
 External templates will be searched by the renderer in a C<templates>
-directory if it exists.
+directory if it exists and have a higher precedence than those in the C<DATA>
+section.
 
   use Mojolicious::Lite;
 
@@ -917,7 +923,7 @@ and return them with L<Mojolicious::Controller/"send">.
   <html>
     <head>
       <title>Echo</title>
-      %= javascript begin
+      <script>
         var ws = new WebSocket('<%= url_for('echo')->to_abs %>');
         ws.onmessage = function (event) {
           document.body.innerHTML += JSON.parse(event.data).msg;
@@ -925,7 +931,7 @@ and return them with L<Mojolicious::Controller/"send">.
         ws.onopen = function (event) {
           ws.send(JSON.stringify({msg: 'I ♥ Mojolicious!'}));
         };
-      % end
+      </script>
     </head>
   </html>
 
@@ -24,9 +24,8 @@ sub _headers {
 
   # All headers need to match
   my $headers = $c->req->headers;
-  while (my ($name, $pattern) = each %$patterns) {
-    return undef unless _check(scalar $headers->header($name), $pattern);
-  }
+  _check(scalar $headers->header($_), $patterns->{$_}) || return undef
+    for keys %$patterns;
   return 1;
 }
 
@@ -2,7 +2,7 @@ package Mojolicious::Plugin::TagHelpers;
 use Mojo::Base 'Mojolicious::Plugin';
 
 use Mojo::ByteStream;
-use Mojo::Util qw(deprecated xml_escape);
+use Mojo::Util 'xml_escape';
 use Scalar::Util 'blessed';
 
 sub register {
@@ -157,13 +157,6 @@ sub _select_field {
   my $groups = '';
   for my $group (@$options) {
 
-    # DEPRECATED in Top Hat!
-    if (ref $group eq 'HASH') {
-      deprecated
-        'hash references are DEPRECATED in favor of Mojo::Collection objects';
-      $group = Mojo::Collection->new(each %$group);
-    }
-
     # "optgroup" tag
     if (blessed $group && $group->isa('Mojo::Collection')) {
       my ($label, $values, %attrs) = @$group;
@@ -227,6 +227,7 @@ sub _render_template {
 
   # Find handler and render
   my $handler = $options->{handler} ||= $self->template_handler($options);
+  return undef unless $handler;
   if (my $renderer = $self->handlers->{$handler}) {
     return 1 if $renderer->($self, $c, $output, $options);
   }
@@ -52,8 +52,9 @@ sub file {
 sub serve {
   my ($self, $c, $rel) = @_;
   return undef unless my $asset = $self->file($rel);
-  my $type = $rel =~ /\.(\w+)$/ ? $c->app->types->type($1) : undef;
-  $c->res->headers->content_type($type || 'text/plain');
+  my $types = $c->app->types;
+  my $type = $rel =~ /\.(\w+)$/ ? $types->type($1) : undef;
+  $c->res->headers->content_type($type || $types->type('txt'));
   return !!$self->serve_asset($c, $asset);
 }
 
@@ -8,7 +8,7 @@
     %= javascript '/mojo/jquery/jquery.js'
     %= javascript '/mojo/prettify/run_prettify.js'
     %= stylesheet '/mojo/prettify/prettify-mojo-dark.css'
-    %= stylesheet begin
+    <style>
       a img { border: 0 }
       body {
         background: url(<%= url_for '/mojo/pinstripe-light.png' %>);
@@ -70,17 +70,17 @@
       }
       .value { padding-left: 1em }
       .wide { width: 100% }
-      #footer {
-        padding-top: 1em;
-        text-align: center;
-      }
-      #nothing { padding-top: 60px }
-      #showcase > pre {
+      #error {
         font: 1.5em 'Helvetica Neue', Helvetica, sans-serif;
         font-weight: 300;
         margin: 0;
         text-shadow: #333 0 1px 0;
       }
+      #footer {
+        padding-top: 1em;
+        text-align: center;
+      }
+      #nothing { padding-top: 60px }
       #showcase table { margin-top: 1em }
       #showcase td {
         padding-top: 0;
@@ -111,11 +111,11 @@
         max-width: 1000px;
         margin: 0 auto;
       }
-    % end
+    </style>
   </head>
   <body>
     %= include inline => app->renderer->_bundled('mojobar')
-    %= javascript begin
+    <script>
       function mojoDrawer (handle, drawer) {
         $(handle).click(function() {
           $(drawer).slideToggle('slow');
@@ -129,7 +129,7 @@
         mojoDrawer('#trace', '#frames');
         mojoDrawer('#more', '#infos');
       });
-    % end
+    </script>
     <div id="wrapperlicious">
       % my $kv = begin
         % my ($key, $value) = @_;
@@ -150,7 +150,7 @@
           % end
         % end
         <div id="showcase" class="box code spaced">
-          <pre><%= $exception->message %></pre>
+          <pre id="error"><%= $exception->message %></pre>
           <div id="context" class="more">
             <table>
               % for my $line (@{$exception->lines_before}) {
@@ -177,7 +177,7 @@
               </table>
             </div>
             <div class="tap">tap for more</div>
-            %= javascript begin
+            <script>
               var current = '#context';
               $('#showcase').click(function() {
                 $(current).slideToggle('slow', function() {
@@ -191,7 +191,7 @@
                 });
               });
               $('#insight').toggle();
-            % end
+            </script>
           % }
         </div>
         <div id="trace" class="box spaced">
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head><title>Server error</title></head>
-   %= stylesheet begin
+  <style>
       body { background-color: #caecf6 }
       #raptor {
         background: url(<%= url_for '/mojo/failraptor.png' %>);
@@ -13,6 +13,6 @@
         top: 50%;
         width: 743px;
       }
-    % end
+    </style>
   <body><div id="raptor"></div></body>
 </html>
@@ -1,6 +1,6 @@
 %= javascript '/mojo/jquery/jquery.js'
 <div id="mojobar">
-  %= stylesheet scoped => 'scoped', begin
+  <style scoped="scoped">
     #mojobar {
       background-color: #1a1a1a;
       background: -webkit-linear-gradient(top, #2a2a2a 0%, #000 100%);
@@ -46,7 +46,7 @@
     #mojobar-links input:focus { outline: 0 }
     #mojobar-links form { display: inline }
     .animated { transition: top 0.2s ease-in-out }
-  % end
+  </style>
   <div id="mojobar-logo">
     %= link_to 'http://mojolicio.us' => begin
       %= image '/mojo/logo-white.png', alt => 'Mojolicious logo'
@@ -67,7 +67,7 @@
     %= end
   </div>
 </div>
-%= javascript begin
+<script>
   var mojobar = $('#mojobar');
   var mojobarHeight = mojobar.outerHeight();
   function fixOffset() {
@@ -120,4 +120,4 @@
       fixOffset();
     });
   });
-% end
+</script>
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head><title>Page not found</title></head>
-   %= stylesheet begin
+  <style>
       a img { border: 0 }
       body { background-color: #caecf6 }
       #noraptor {
@@ -19,7 +19,7 @@
         top: 50%;
         width: 306px;
       }
-    % end
+    </style>
   <body>
     %= link_to url_for->base => begin
       %= image '/mojo/noraptor.png', alt => 'Bye!', id => 'noraptor'
@@ -4,7 +4,7 @@
     <title><%= $title %></title>
     %= javascript '/mojo/prettify/run_prettify.js'
     %= stylesheet '/mojo/prettify/prettify-mojo-light.css'
-    %= stylesheet begin
+    <style>
       a { color: inherit }
       a:hover { color: #2a2a2a }
       a img { border: 0 }
@@ -75,7 +75,7 @@
       h1:hover .permalink, h2:hover .permalink, h3:hover .permalink {
         display: block;
       }
-    % end
+    </style>
   </head>
   <body>
     %= include inline => app->renderer->_bundled('mojobar')
@@ -43,7 +43,7 @@ has types     => sub { Mojolicious::Types->new };
 has validator => sub { Mojolicious::Validator->new };
 
 our $CODENAME = 'Top Hat';
-our $VERSION  = '4.91';
+our $VERSION  = '4.93';
 
 sub AUTOLOAD {
   my $self = shift;
@@ -988,6 +988,7 @@ the terms of the Artistic License version 2.0.
 
 =head1 SEE ALSO
 
-L<Mojolicious::Guides>, L<http://mojolicio.us>.
+L<https://github.com/kraih/mojo>, L<Mojolicious::Guides>,
+L<http://mojolicio.us>.
 
 =cut
@@ -351,7 +351,8 @@ $t->get_ok('/' => {'X-Test' => 'Hi there!'})->status_is(404)
 
 # Static file /another/file (no extension)
 $t->get_ok('/another/file')->status_is(200)
-  ->header_is(Server => 'Mojolicious (Perl)')->content_type_is('text/plain')
+  ->header_is(Server => 'Mojolicious (Perl)')
+  ->content_type_is('text/plain;charset=UTF-8')
   ->content_like(qr/Hello Mojolicious!/);
 
 # Static directory /another
@@ -52,7 +52,8 @@ hook before_dispatch => sub {
 # Custom dispatcher /custom
 hook before_dispatch => sub {
   my $c = shift;
-  $c->render(text => $c->param('a'), status => 205)
+  $c->render_maybe
+    or $c->render(text => $c->param('a'), status => 205)
     if $c->req->url->path->contains('/custom');
 };
 
@@ -166,7 +166,7 @@ like $log, qr/dead template with layout!/, 'right result';
 $t->get_ok('/dead_action')->status_is(500)
   ->content_type_is('text/html;charset=UTF-8')
   ->content_like(qr!get &#39;/dead_action&#39;!)
-  ->content_like(qr/dead action!/);
+  ->content_like(qr/dead action!/)->text_is('#error' => "dead action!\n");
 like $log, qr/dead action!/, 'right result';
 
 # Dead action with different format
@@ -757,7 +757,8 @@ $t->get_ok('/inline/ep/partial')->status_is(200)
   ->content_is("♥just ♥\nworks!\n");
 
 # Render static file outside of public directory
-$t->get_ok('/source')->status_is(200)->header_isnt('X-Missing' => 1)
+$t->get_ok('/source')->status_is(200)
+  ->content_type_is('text/plain;charset=UTF-8')->header_isnt('X-Missing' => 1)
   ->content_like(qr!get_ok\('/source!);
 
 # File does not exist