The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
Build.PL 1914
Changes 5170
MANIFEST 01
META.json 810
META.yml 1314
README.md 1010
lib/Router/Simple/Route.pm 02
lib/Router/Simple.pm 716
t/14_ending_slash.t 052
9 files changed (This is a version diff) 108189
@@ -12,8 +12,6 @@ use utf8;
 use Module::Build;
 use File::Basename;
 use File::Spec;
-use CPAN::Meta;
-use CPAN::Meta::Prereqs;
 
 my %args = (
     license              => 'perl',
@@ -33,6 +31,8 @@ my %args = (
 
     test_files           => ((-d '.git' || $ENV{RELEASE_TESTING}) && -d 'xt') ? 't/ xt/' : 't/',
     recursive_test_files => 1,
+
+
 );
 if (-d 'share') {
     $args{share_dir} = 'share';
@@ -51,20 +51,15 @@ my $builder = Module::Build->subclass(
 )->new(%args);
 $builder->create_build_script();
 
-my $mbmeta = CPAN::Meta->load_file('MYMETA.json');
-my $meta = CPAN::Meta->load_file('META.json');
-my $prereqs_hash = CPAN::Meta::Prereqs->new(
-    $meta->prereqs
-)->with_merged_prereqs(
-    CPAN::Meta::Prereqs->new($mbmeta->prereqs)
-)->as_string_hash;
-my $mymeta = CPAN::Meta->new(
-    {
-        %{$meta->as_struct},
-        prereqs => $prereqs_hash
-    }
-);
-print "Merging cpanfile prereqs to MYMETA.yml\n";
-$mymeta->save('MYMETA.yml', { version => 1.4 });
-print "Merging cpanfile prereqs to MYMETA.json\n";
-$mymeta->save('MYMETA.json', { version => 2 });
+use File::Copy;
+
+print "cp META.json MYMETA.json\n";
+copy("META.json","MYMETA.json") or die "Copy failed(META.json): $!";
+
+if (-f 'META.yml') {
+    print "cp META.yml MYMETA.yml\n";
+    copy("META.yml","MYMETA.yml") or die "Copy failed(META.yml): $!";
+} else {
+    print "There is no META.yml... You may install this module from the repository...\n";
+}
+
@@ -1,4 +1,19 @@
-Revision history for Perl extension Router::Simple
+Revision history for Perl module Router::Simple
+
+0.16 2014-10-27T01:39:47Z
+
+    commit 4cce76f681517d9ae10d2509899bf0e9abd4db5d
+    commit 47ca5b5141b2ae863b62989755e5f3a17edf37df
+    Author: Fayland Lam <fayland@gmail.com>
+    Date:   Sun Oct 26 17:44:51 2014 +0800
+
+        Added directory_slash option.
+
+    commit 379502c868db3bd37c3518e288d64223d4012417
+    Author: Neil Bowers <neil@bowers.com>
+    Date:   Wed Oct 2 23:35:04 2013 +0100
+
+        Reformatted as per CPAN::Changes::Spec
 
 0.15 2013-09-29T02:29:15Z
 
@@ -7,82 +22,86 @@ Revision history for Perl extension Router::Simple
     - typo fix
       (David Steinbrunner)
 
-0.14
+0.14 2012-12-05
+
+    - added 'routes' acecessor for Router::Simple class.
+      And switch to Class::Accessor::Lite.
 
-        - added 'routes' acecessor for Router::Simple class.
-          And switch to Class::Accessor::Lite.
+0.13 2012-10-26
 
-0.13
+    - Added ->method_not_allowed()
 
-        - Added ->method_not_allowed()
+0.12 2012-10-25
 
-0.12
+    - minor pod fix
+      (book++)
 
-        - minor pod fix
-          (book++)
+0.11 2012-10-23
 
-0.11
+    - Added warnings when regexp contains parens.
+      i.e. following code does not works.
+      /{year:(\d+)}/{month:(\d+)}{day:(\d+)}
 
-        - Added warnings when regexp contains parens.
-          i.e. following code does not works.
-          /{year:(\d+)}/{month:(\d+)}{day:(\d+)}
+    Q. Why don't you croak?
+    A. I don't want to break existed code.
+       But I may change this feature in future version.
 
-          Q. Why don't you croak?
-          A. I don't want to break existed code.
-             But I may change this feature in future version.
+       (reported by ktat++)
 
-          (reported by ktat++)
+0.10 2012-10-10
+
+    - handle empty PATH_INFO as root
+      (tokuhirom)
 
-0.10
+0.09 2011-05-14
 
-        - handle empty PATH_INFO as root
-          (tokuhirom)
+    - modernize package (no feature changes)
 
-0.09
+0.08 2011-02-16
 
-        - modernize package(no feature changes)
+    - fix "Can't use an undefined value as a HASH reference" error
+      for $route->{dest}
+      (tomi-ru++)
 
-0.08
+0.07 2010-09-02
 
-        - fix "Can't use an undefined value as a HASH reference" error for $route->{dest}
-          (tomi-ru++)
+    - modified to allow to use extended regexp (?:including ":" - colon)
+      as pattern
+      (nipotan)
 
-0.07
+0.06 2010-06-14
 
-        - modified to allow to use extended regexp (?:including ":" - colon) as pattern
-          (nipotan)
+    - require Test::More 0.88 or later for using done_testing.
+      (reported by kazeburo++)
 
-0.06
+0.05 2010-03-21
 
-        - require Test::More 0.88 or later for using done_testing.
-          (reported by kazeburo++)
+    [FEATURE ENHANCEMENTS]
+    - added Router::Simple::Route->match($env) method.
+    - added $route->routematch() method.
 
-0.05
+    [INCOMPATIBLE CHANGES]
+    - remove url_for().
 
-        [FEATURE ENHANCEMENTS]
-        - added Router::Simple::Route->match($env) method.
-        - added $route->routematch() method.
+0.04 2010-03-20
 
-        [INCOMPATIBLE CHANGES]
-        - remove url_for().
+    (no feature changes)
+    - some doc fixed(miyagawa++)
+    - added test case(miyagawa++)
 
-0.04
+0.03 2010-03-19
 
-        (no feature changes)
-        - some doc fixed(miyagawa++)
-        - added test case(miyagawa++)
+    [INCOMPATIBLE CHANGES]
+    - make 'submapper' as more consistent.
+    - ->match() returns flat hashref(suggested by miyagawa++)
+    - added 'on_match' callback function(suggested by miyagawa++)
+    - ->connect() does not treat any keys in $destination.
 
-0.03
+0.02 2010-03-19
 
-        [INCOMPATIBLE CHANGES]
-        - make 'submapper' as more consistent.
-        - ->match() returns flat hashref(suggested by miyagawa++)
-        - added 'on_match' callback function(suggested by miyagawa++)
-        - ->connect() does not treat any keys in $destination.
+    - fixed english docs(Sartak++)
 
-0.02
+0.01 2010-03-19
 
-        - fixed english docs(Sartak++)
+    - original version
 
-0.01    Wed Mar 17 08:36:14 2010
-        - original version
@@ -23,6 +23,7 @@ t/10_routematch.t
 t/11_ext_regexp.t
 t/12_paren_warnings.t
 t/13_method_not_allowed.t
+t/14_ending_slash.t
 xt/02_perlcritic.t
 xt/04_synopsis.t
 META.yml
@@ -4,7 +4,7 @@
       "Tokuhiro Matsuno <tokuhirom AAJKLFJEF@ GMAIL COM>"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "Minilla/v0.6.7",
+   "generated_by" : "Minilla/v2.2.0",
    "license" : [
       "perl_5"
    ],
@@ -21,21 +21,21 @@
          "share",
          "eg",
          "examples",
-         "author"
+         "author",
+         "builder"
       ]
    },
    "prereqs" : {
       "configure" : {
          "requires" : {
-            "CPAN::Meta" : "0",
-            "CPAN::Meta::Prereqs" : "0",
             "Module::Build" : "0.38"
          }
       },
       "develop" : {
          "requires" : {
             "Test::CPAN::Meta" : "0",
-            "Test::MinimumVersion" : "0.10108",
+            "Test::MinimumVersion::Fast" : "0.04",
+            "Test::PAUSE::Permissions" : "0.04",
             "Test::Pod" : "1.41",
             "Test::Spellunker" : "v0.2.7"
          }
@@ -58,7 +58,7 @@
    "provides" : {
       "Router::Simple" : {
          "file" : "lib/Router/Simple.pm",
-         "version" : "0.15"
+         "version" : "0.16"
       },
       "Router::Simple::Declare" : {
          "file" : "lib/Router/Simple/Declare.pm"
@@ -81,7 +81,7 @@
          "web" : "https://github.com/tokuhirom/p5-router-simple"
       }
    },
-   "version" : "0.15",
+   "version" : "0.16",
    "x_contributors" : [
       "Shawn M Moore <sartak@gmail.com>",
       "Tatsuhiko Miyagawa <miyagawa@bulknews.net>",
@@ -89,6 +89,8 @@
       "Naoki Tomita <tomita@cpan.org>",
       "Philippe Bruhat (BooK) <book@cpan.org>",
       "David Steinbrunner <dsteinbrunner@pobox.com>",
-      "tokuhirom <tokuhirom@gmail.com>"
+      "Neil Bowers <neil@bowers.com>",
+      "Fayland Lam <fayland@gmail.com>",
+      "Tokuhiro Matsuno <tokuhirom@gmail.com>"
    ]
 }
@@ -3,17 +3,15 @@ abstract: 'simple HTTP router'
 author:
   - 'Tokuhiro Matsuno <tokuhirom AAJKLFJEF@ GMAIL COM>'
 build_requires:
-  Test::More: 0.98
+  Test::More: '0.98'
 configure_requires:
-  CPAN::Meta: 0
-  CPAN::Meta::Prereqs: 0
-  Module::Build: 0.38
+  Module::Build: '0.38'
 dynamic_config: 0
-generated_by: 'Minilla/v0.6.7, CPAN::Meta::Converter version 2.132510'
+generated_by: 'Minilla/v2.2.0, CPAN::Meta::Converter version 2.141520'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
+  version: '1.4'
 name: Router-Simple
 no_index:
   directory:
@@ -24,10 +22,11 @@ no_index:
     - eg
     - examples
     - author
+    - builder
 provides:
   Router::Simple:
     file: lib/Router/Simple.pm
-    version: 0.15
+    version: '0.16'
   Router::Simple::Declare:
     file: lib/Router/Simple/Declare.pm
   Router::Simple::Route:
@@ -35,16 +34,16 @@ provides:
   Router::Simple::SubMapper:
     file: lib/Router/Simple/SubMapper.pm
 requires:
-  Class::Accessor::Lite: 0.05
-  List::Util: 0
-  Scalar::Util: 0
-  parent: 0
+  Class::Accessor::Lite: '0.05'
+  List::Util: '0'
+  Scalar::Util: '0'
+  parent: '0'
   perl: 5.008_001
 resources:
   bugtracker: https://github.com/tokuhirom/p5-router-simple/issues
   homepage: https://github.com/tokuhirom/p5-router-simple
   repository: git://github.com/tokuhirom/p5-router-simple.git
-version: 0.15
+version: '0.16'
 x_contributors:
   - 'Shawn M Moore <sartak@gmail.com>'
   - 'Tatsuhiko Miyagawa <miyagawa@bulknews.net>'
@@ -52,4 +51,6 @@ x_contributors:
   - 'Naoki Tomita <tomita@cpan.org>'
   - 'Philippe Bruhat (BooK) <book@cpan.org>'
   - 'David Steinbrunner <dsteinbrunner@pobox.com>'
-  - 'tokuhirom <tokuhirom@gmail.com>'
+  - 'Neil Bowers <neil@bowers.com>'
+  - 'Fayland Lam <fayland@gmail.com>'
+  - 'Tokuhiro Matsuno <tokuhirom@gmail.com>'
@@ -30,7 +30,7 @@ it's easy to use with PSGI supporting web frameworks.
 
 # HOW TO WRITE A ROUTING RULE
 
-## plain string 
+## plain string
 
     $router->connect( '/foo', { controller => 'Root', action => 'foo' } );
 
@@ -108,11 +108,11 @@ are stored in the special key `splat`.
 
     - method
 
-        'method' is an ArrayRef\[String\] or String that matches __REQUEST\_METHOD__ in $req.
+        'method' is an ArrayRef\[String\] or String that matches **REQUEST\_METHOD** in $req.
 
     - host
 
-        'host' is a String or Regexp that matches __HTTP\_HOST__ in $req.
+        'host' is a String or Regexp that matches **HTTP\_HOST** in $req.
 
     - on\_match
 
@@ -142,7 +142,7 @@ are stored in the special key `splat`.
 
         $router->submapper('/entry/', {controller => 'Entry'})
 
-    This method is shorthand for creating new instance of [Router::Simple::Submapper](http://search.cpan.org/perldoc?Router::Simple::Submapper).
+    This method is shorthand for creating new instance of [Router::Simple::Submapper](https://metacpan.org/pod/Router::Simple::Submapper).
 
     The arguments will be passed to `Router::Simple::SubMapper->new(%args)`.
 
@@ -150,7 +150,7 @@ are stored in the special key `splat`.
 
     Matches a URL against one of the contained routes.
 
-    The parameter is either a [PSGI](http://search.cpan.org/perldoc?PSGI) $env or a plain string that
+    The parameter is either a [PSGI](https://metacpan.org/pod/PSGI) $env or a plain string that
     represents a path.
 
     This method returns a plain hashref that would look like:
@@ -168,7 +168,7 @@ are stored in the special key `splat`.
     Match a URL against one of the routes contained.
 
     Will return undef if no valid match is found, otherwise a
-    result hashref and a [Router::Simple::Route](http://search.cpan.org/perldoc?Router::Simple::Route) object is returned.
+    result hashref and a [Router::Simple::Route](https://metacpan.org/pod/Router::Simple::Route) object is returned.
 
 - `$router->as_string()`
 
@@ -198,13 +198,13 @@ Shawn M Moore
 
 Router::Simple is inspired by [routes.py](http://routes.groovie.org/).
 
-[Path::Dispatcher](http://search.cpan.org/perldoc?Path::Dispatcher) is similar, but so complex.
+[Path::Dispatcher](https://metacpan.org/pod/Path::Dispatcher) is similar, but so complex.
 
-[Path::Router](http://search.cpan.org/perldoc?Path::Router) is heavy. It depends on [Moose](http://search.cpan.org/perldoc?Moose).
+[Path::Router](https://metacpan.org/pod/Path::Router) is heavy. It depends on [Moose](https://metacpan.org/pod/Moose).
 
-[HTTP::Router](http://search.cpan.org/perldoc?HTTP::Router) has many dependencies. It is not well documented.
+[HTTP::Router](https://metacpan.org/pod/HTTP::Router) has many dependencies. It is not well documented.
 
-[HTTPx::Dispatcher](http://search.cpan.org/perldoc?HTTPx::Dispatcher) is my old one. It does not provide an OO-ish interface.
+[HTTPx::Dispatcher](https://metacpan.org/pod/HTTPx::Dispatcher) is my old one. It does not provide an OO-ish interface.
 
 # THANKS TO
 
@@ -63,6 +63,8 @@ sub new {
                     quotemeta($4);
                 }
             !gex;
+            # for example, pattern '/comment/' will both match '/comment/' and '/comment'
+            $pattern .= '?' if $opt->{directory_slash} and $pattern =~ m{\/$};
             qr{^$pattern$};
         }
     };
@@ -2,24 +2,33 @@ package Router::Simple;
 use strict;
 use warnings;
 use 5.00800;
-our $VERSION = '0.15';
+our $VERSION = '0.16';
 use Router::Simple::SubMapper;
 use Router::Simple::Route;
 use List::Util qw/max/;
 use Carp ();
 
 use Class::Accessor::Lite 0.05 (
-    ro => [qw(routes)],
+    new => 1,
+    ro => [qw(routes directory_slash)],
 );
 
 our $_METHOD_NOT_ALLOWED;
 
-sub new {
-    bless {routes => []}, shift;
-}
-
 sub connect {
     my $self = shift;
+
+    if ($self->{directory_slash}) {
+        # connect([$name, ]$pattern[, \%dest[, \%opt]])
+        if (@_ == 1 || ref $_[1]) {
+            unshift(@_, undef);
+        }
+
+        # \%opt
+        $_[3] ||= {};
+        $_[3]->{directory_slash} = 1;
+    }
+
     my $route = Router::Simple::Route->new(@_);
     push @{ $self->{routes} }, $route;
     return $self;
@@ -126,7 +135,7 @@ it's easy to use with PSGI supporting web frameworks.
 
 =head1 HOW TO WRITE A ROUTING RULE
 
-=head2 plain string 
+=head2 plain string
 
     $router->connect( '/foo', { controller => 'Root', action => 'foo' } );
 
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+use Router::Simple;
+use Test::More;
+
+my $r = Router::Simple->new({ directory_slash => 1 });
+$r->connect('blog_monthly', '/blog/{year}/{month}/', {controller => 'Blog', action => 'monthly'}, {method => 'GET'});
+$r->connect('/blog/{year:\d{1,4}}/{month:\d{2}}/{day:\d\d}/', {controller => 'Blog', action => 'daily'}, {method => 'GET'});
+$r->connect('/comment/', {controller => 'Comment', 'action' => 'create'}, {method => 'POST'});
+$r->connect('/:controller/:action/');
+
+foreach my $es ('', '/') {
+    is_deeply(
+        $r->match( +{ PATH_INFO => '/blog/2010/03' . $es, HTTP_HOST => 'localhost', REQUEST_METHOD => 'GET' } ) || undef,
+        {
+            controller => 'Blog',
+            action     => 'monthly',
+            year => 2010,
+            month => '03'
+        },
+        'blog monthly'
+    );
+    is_deeply(
+        $r->match( +{ PATH_INFO => '/blog/2010/03/04' . $es, HTTP_HOST => 'localhost', REQUEST_METHOD => 'GET' } ) || undef,
+        {
+            controller => 'Blog',
+            action     => 'daily',
+            year => 2010, month => '03', day => '04',
+        },
+        'daily'
+    );
+    is_deeply(
+        $r->match( +{ PATH_INFO => '/blog/2010/03' . $es, HTTP_HOST => 'localhost', REQUEST_METHOD => 'POST' } ) || undef,
+        undef
+    );
+    is_deeply(
+        $r->match( +{ PATH_INFO => '/comment' . $es, HTTP_HOST => 'localhost', REQUEST_METHOD => 'POST' } ) || undef,
+        {
+            controller => 'Comment',
+            action     => 'create',
+        }
+    );
+    is_deeply(
+        $r->match( +{ PATH_INFO => '/foo/bar' . $es, HTTP_HOST => 'localhost', REQUEST_METHOD => 'GET' } ) || undef,
+        {
+            controller => 'foo',
+            action     => 'bar',
+        }
+    );
+}
+
+done_testing;