[Bps-public-commit] Path-Dispatcher branch, lazy-dispatch, created. eb5928a6bbc288143532fadba53249fa8c7a62e6

sartak at bestpractical.com sartak at bestpractical.com
Fri Nov 6 20:52:24 EST 2009


The branch, lazy-dispatch has been created
        at  eb5928a6bbc288143532fadba53249fa8c7a62e6 (commit)

- Log -----------------------------------------------------------------
commit 9b95e015943f438d1b653b590cded76ea1e6d92c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 16:53:34 2008 +0000

    Project layout

diff --git a/Changes b/Changes
new file mode 100644
index 0000000..e69de29
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100755
index 0000000..9bbf979
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,9 @@
+use inc::Module::Install;
+
+name           'Path-Dispatcher';
+all_from       'lib/Path/Dispatcher.pm';
+
+build_requires 'Test::More';
+
+WriteAll;
+
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
new file mode 100644
index 0000000..8fb6b20
--- /dev/null
+++ b/inc/Module/Install.pm
@@ -0,0 +1,353 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+BEGIN {
+	require 5.004;
+}
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+	# All Module::Install core packages now require synchronised versions.
+	# This will be used to ensure we don't accidentally load old or
+	# different versions of modules.
+	# This is not enforced yet, but will be some time in the next few
+	# releases once we can make sure it won't clash with custom
+	# Module::Install extensions.
+	$VERSION = '0.75';
+
+	*inc::Module::Install::VERSION = *VERSION;
+	@inc::Module::Install::ISA     = __PACKAGE__;
+
+}
+
+
+
+
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) { die <<"END_DIE" }
+
+Please invoke ${\__PACKAGE__} with:
+
+	use inc::${\__PACKAGE__};
+
+not:
+
+	use ${\__PACKAGE__};
+
+END_DIE
+
+
+
+
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+
+
+
+
+
+# Build.PL was formerly supported, but no longer is due to excessive
+# difficulty in implementing every single feature twice.
+if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+
+Module::Install no longer supports Build.PL.
+
+It was impossible to maintain duel backends, and has been deprecated.
+
+Please remove all Build.PL files and only use the Makefile.PL installer.
+
+END_DIE
+
+
+
+
+
+# To save some more typing in Module::Install installers, every...
+# use inc::Module::Install
+# ...also acts as an implicit use strict.
+$^H |= strict::bits(qw(refs subs vars));
+
+
+
+
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+sub autoload {
+	my $self = shift;
+	my $who  = $self->_caller;
+	my $cwd  = Cwd::cwd();
+	my $sym  = "${who}::AUTOLOAD";
+	$sym->{$cwd} = sub {
+		my $pwd = Cwd::cwd();
+		if ( my $code = $sym->{$pwd} ) {
+			# delegate back to parent dirs
+			goto &$code unless $cwd eq $pwd;
+		}
+		$$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+		unshift @_, ( $self, $1 );
+		goto &{$self->can('call')} unless uc($1) eq $1;
+	};
+}
+
+sub import {
+	my $class = shift;
+	my $self  = $class->new(@_);
+	my $who   = $self->_caller;
+
+	unless ( -f $self->{file} ) {
+		require "$self->{path}/$self->{dispatch}.pm";
+		File::Path::mkpath("$self->{prefix}/$self->{author}");
+		$self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+		$self->{admin}->init;
+		@_ = ($class, _self => $self);
+		goto &{"$self->{name}::import"};
+	}
+
+	*{"${who}::AUTOLOAD"} = $self->autoload;
+	$self->preload;
+
+	# Unregister loader and worker packages so subdirs can use them again
+	delete $INC{"$self->{file}"};
+	delete $INC{"$self->{path}.pm"};
+
+	return 1;
+}
+
+sub preload {
+	my $self = shift;
+	unless ( $self->{extensions} ) {
+		$self->load_extensions(
+			"$self->{prefix}/$self->{path}", $self
+		);
+	}
+
+	my @exts = @{$self->{extensions}};
+	unless ( @exts ) {
+		my $admin = $self->{admin};
+		@exts = $admin->load_all_extensions;
+	}
+
+	my %seen;
+	foreach my $obj ( @exts ) {
+		while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+			next unless $obj->can($method);
+			next if $method =~ /^_/;
+			next if $method eq uc($method);
+			$seen{$method}++;
+		}
+	}
+
+	my $who = $self->_caller;
+	foreach my $name ( sort keys %seen ) {
+		*{"${who}::$name"} = sub {
+			${"${who}::AUTOLOAD"} = "${who}::$name";
+			goto &{"${who}::AUTOLOAD"};
+		};
+	}
+}
+
+sub new {
+	my ($class, %args) = @_;
+
+	# ignore the prefix on extension modules built from top level.
+	my $base_path = Cwd::abs_path($FindBin::Bin);
+	unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+		delete $args{prefix};
+	}
+
+	return $args{_self} if $args{_self};
+
+	$args{dispatch} ||= 'Admin';
+	$args{prefix}   ||= 'inc';
+	$args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+	$args{bundle}   ||= 'inc/BUNDLES';
+	$args{base}     ||= $base_path;
+	$class =~ s/^\Q$args{prefix}\E:://;
+	$args{name}     ||= $class;
+	$args{version}  ||= $class->VERSION;
+	unless ( $args{path} ) {
+		$args{path}  = $args{name};
+		$args{path}  =~ s!::!/!g;
+	}
+	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+	$args{wrote}      = 0;
+
+	bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+	my ($self, $method) = @_;
+
+	$self->load_extensions(
+		"$self->{prefix}/$self->{path}", $self
+	) unless $self->{extensions};
+
+	foreach my $obj (@{$self->{extensions}}) {
+		return $obj if $obj->can($method);
+	}
+
+	my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+	my $obj = $admin->load($method, 1);
+	push @{$self->{extensions}}, $obj;
+
+	$obj;
+}
+
+sub load_extensions {
+	my ($self, $path, $top) = @_;
+
+	unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+		unshift @INC, $self->{prefix};
+	}
+
+	foreach my $rv ( $self->find_extensions($path) ) {
+		my ($file, $pkg) = @{$rv};
+		next if $self->{pathnames}{$pkg};
+
+		local $@;
+		my $new = eval { require $file; $pkg->can('new') };
+		unless ( $new ) {
+			warn $@ if $@;
+			next;
+		}
+		$self->{pathnames}{$pkg} = delete $INC{$file};
+		push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+	}
+
+	$self->{extensions} ||= [];
+}
+
+sub find_extensions {
+	my ($self, $path) = @_;
+
+	my @found;
+	File::Find::find( sub {
+		my $file = $File::Find::name;
+		return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+		my $subpath = $1;
+		return if lc($subpath) eq lc($self->{dispatch});
+
+		$file = "$self->{path}/$subpath.pm";
+		my $pkg = "$self->{name}::$subpath";
+		$pkg =~ s!/!::!g;
+
+		# If we have a mixed-case package name, assume case has been preserved
+		# correctly.  Otherwise, root through the file to locate the case-preserved
+		# version of the package name.
+		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+			my $content = Module::Install::_read($subpath . '.pm');
+			my $in_pod  = 0;
+			foreach ( split //, $content ) {
+				$in_pod = 1 if /^=\w/;
+				$in_pod = 0 if /^=cut/;
+				next if ($in_pod || /^=cut/);  # skip pod text
+				next if /^\s*#/;               # and comments
+				if ( m/^\s*package\s+($pkg)\s*;/i ) {
+					$pkg = $1;
+					last;
+				}
+			}
+		}
+
+		push @found, [ $file, $pkg ];
+	}, $path ) if -d $path;
+
+	@found;
+}
+
+
+
+
+
+#####################################################################
+# Utility Functions
+
+sub _caller {
+	my $depth = 0;
+	my $call  = caller($depth);
+	while ( $call eq __PACKAGE__ ) {
+		$depth++;
+		$call = caller($depth);
+	}
+	return $call;
+}
+
+sub _read {
+	local *FH;
+	open FH, "< $_[0]" or die "open($_[0]): $!";
+	my $str = do { local $/; <FH> };
+	close FH or die "close($_[0]): $!";
+	return $str;
+}
+
+sub _write {
+	local *FH;
+	open FH, "> $_[0]" or die "open($_[0]): $!";
+	foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!" }
+	close FH or die "close($_[0]): $!";
+}
+
+sub _version {
+	my $s = shift || 0;
+	   $s =~ s/^(\d+)\.?//;
+	my $l = $1 || 0;
+	my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g;
+	   $l = $l . '.' . join '', @v if @v;
+	return $l + 0;
+}
+
+1;
+
+# Copyright 2008 Adam Kennedy.
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
new file mode 100644
index 0000000..bd12f2b
--- /dev/null
+++ b/inc/Module/Install/Base.pm
@@ -0,0 +1,70 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.75';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 138
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
new file mode 100644
index 0000000..3f436c7
--- /dev/null
+++ b/inc/Module/Install/Can.pm
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.75';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 157
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
new file mode 100644
index 0000000..1327f35
--- /dev/null
+++ b/inc/Module/Install/Fetch.pm
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.75';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) = 
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) = 
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
new file mode 100644
index 0000000..b7c2ba9
--- /dev/null
+++ b/inc/Module/Install/Makefile.pm
@@ -0,0 +1,245 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.75';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+	shift;
+
+	# Infinite loop protection
+	my @c = caller();
+	if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+		die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+	}
+
+	# In automated testing, always use defaults
+	if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+		local $ENV{PERL_MM_USE_DEFAULT} = 1;
+		goto &ExtUtils::MakeMaker::prompt;
+	} else {
+		goto &ExtUtils::MakeMaker::prompt;
+	}
+}
+
+sub makemaker_args {
+	my $self = shift;
+	my $args = ($self->{makemaker_args} ||= {});
+	  %$args = ( %$args, @_ ) if @_;
+	$args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+	my $self = sShift;
+	my $name = shift;
+	my $args = $self->makemaker_args;
+	$args->{name} = defined $args->{$name}
+		? join( ' ', $args->{name}, @_ )
+		: join( ' ', @_ );
+}
+
+sub build_subdirs {
+	my $self    = shift;
+	my $subdirs = $self->makemaker_args->{DIR} ||= [];
+	for my $subdir (@_) {
+		push @$subdirs, $subdir;
+	}
+}
+
+sub clean_files {
+	my $self  = shift;
+	my $clean = $self->makemaker_args->{clean} ||= {};
+	  %$clean = (
+		%$clean, 
+		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+	);
+}
+
+sub realclean_files {
+	my $self      = shift;
+	my $realclean = $self->makemaker_args->{realclean} ||= {};
+	  %$realclean = (
+		%$realclean, 
+		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+	);
+}
+
+sub libs {
+	my $self = shift;
+	my $libs = ref $_[0] ? shift : [ shift ];
+	$self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+	my $self = shift;
+	$self->makemaker_args( INC => shift );
+}
+
+my %test_dir = ();
+
+sub _wanted_t {
+	/\.t$/ and -f $_ and $test_dir{$File::Find::dir} = 1;
+}
+
+sub tests_recursive {
+	my $self = shift;
+	if ( $self->tests ) {
+		die "tests_recursive will not work if tests are already defined";
+	}
+	my $dir = shift || 't';
+	unless ( -d $dir ) {
+		die "tests_recursive dir '$dir' does not exist";
+	}
+	%test_dir = ();
+	require File::Find;
+	File::Find::find( \&_wanted_t, $dir );
+	$self->tests( join ' ', map { "$_/*.t" } sort keys %test_dir );
+}
+
+sub write {
+	my $self = shift;
+	die "&Makefile->write() takes no arguments\n" if @_;
+
+	# Make sure we have a new enough
+	require ExtUtils::MakeMaker;
+	$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION );
+
+	# Generate the 
+	my $args = $self->makemaker_args;
+	$args->{DISTNAME} = $self->name;
+	$args->{NAME}     = $self->module_name || $self->name;
+	$args->{VERSION}  = $self->version;
+	$args->{NAME}     =~ s/-/::/g;
+	if ( $self->tests ) {
+		$args->{test} = { TESTS => $self->tests };
+	}
+	if ($] >= 5.005) {
+		$args->{ABSTRACT} = $self->abstract;
+		$args->{AUTHOR}   = $self->author;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+		$args->{NO_META} = 1;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+		$args->{SIGN} = 1;
+	}
+	unless ( $self->is_admin ) {
+		delete $args->{SIGN};
+	}
+
+	# merge both kinds of requires into prereq_pm
+	my $prereq = ($args->{PREREQ_PM} ||= {});
+	%$prereq = ( %$prereq,
+		map { @$_ }
+		map { @$_ }
+		grep $_,
+		($self->configure_requires, $self->build_requires, $self->requires)
+	);
+
+	# Remove any reference to perl, PREREQ_PM doesn't support it
+	delete $args->{PREREQ_PM}->{perl};
+
+	# merge both kinds of requires into prereq_pm
+	my $subdirs = ($args->{DIR} ||= []);
+	if ($self->bundles) {
+		foreach my $bundle (@{ $self->bundles }) {
+			my ($file, $dir) = @$bundle;
+			push @$subdirs, $dir if -d $dir;
+			delete $prereq->{$file};
+		}
+	}
+
+	if ( my $perl_version = $self->perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	$args->{INSTALLDIRS} = $self->installdirs;
+
+	my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+
+	my $user_preop = delete $args{dist}->{PREOP};
+	if (my $preop = $self->admin->preop($user_preop)) {
+		$args{dist} = $preop;
+	}
+
+	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+	$self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+	my $self          = shift;
+	my $makefile_name = shift;
+	my $top_class     = ref($self->_top) || '';
+	my $top_version   = $self->_top->VERSION || '';
+
+	my $preamble = $self->preamble 
+		? "# Preamble by $top_class $top_version\n"
+			. $self->preamble
+		: '';
+	my $postamble = "# Postamble by $top_class $top_version\n"
+		. ($self->postamble || '');
+
+	local *MAKEFILE;
+	open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	my $makefile = do { local $/; <MAKEFILE> };
+	close MAKEFILE or die $!;
+
+	$makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+	$makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+	$makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+	$makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+	$makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+	# Module::Install will never be used to build the Core Perl
+	# Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+	# PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+	$makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+	#$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+	# Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+	$makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
+
+	# XXX - This is currently unused; not sure if it breaks other MM-users
+	# $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+	open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+	close MAKEFILE  or die $!;
+
+	1;
+}
+
+sub preamble {
+	my ($self, $text) = @_;
+	$self->{preamble} = $text . $self->{preamble} if defined $text;
+	$self->{preamble};
+}
+
+sub postamble {
+	my ($self, $text) = @_;
+	$self->{postamble} ||= $self->admin->postamble;
+	$self->{postamble} .= $text if defined $text;
+	$self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 371
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
new file mode 100644
index 0000000..ce26bf6
--- /dev/null
+++ b/inc/Module/Install/Metadata.pm
@@ -0,0 +1,407 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.75';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+	name
+	module_name
+	abstract
+	author
+	version
+	license
+	distribution_type
+	perl_version
+	tests
+	installdirs
+};
+
+my @tuple_keys = qw{
+	configure_requires
+	build_requires
+	requires
+	recommends
+	bundles
+	resources
+};
+
+sub Meta            { shift        }
+sub Meta_ScalarKeys { @scalar_keys }
+sub Meta_TupleKeys  { @tuple_keys  }
+
+foreach my $key (@scalar_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}{$key} if defined wantarray and !@_;
+		$self->{values}{$key} = shift;
+		return $self;
+	};
+}
+
+sub requires {
+	my $self = shift;
+	while ( @_ ) {
+		my $module  = shift or last;
+		my $version = shift || 0;
+		push @{ $self->{values}->{requires} }, [ $module, $version ];
+	}
+	$self->{values}{requires};
+}
+
+sub build_requires {
+	my $self = shift;
+	while ( @_ ) {
+		my $module  = shift or last;
+		my $version = shift || 0;
+		push @{ $self->{values}->{build_requires} }, [ $module, $version ];
+	}
+	$self->{values}{build_requires};
+}
+
+sub configure_requires {
+	my $self = shift;
+	while ( @_ ) {
+		my $module  = shift or last;
+		my $version = shift || 0;
+		push @{ $self->{values}->{configure_requires} }, [ $module, $version ];
+	}
+	$self->{values}->{configure_requires};
+}
+
+sub recommends {
+	my $self = shift;
+	while ( @_ ) {
+		my $module  = shift or last;
+		my $version = shift || 0;
+		push @{ $self->{values}->{recommends} }, [ $module, $version ];
+	}
+	$self->{values}->{recommends};
+}
+
+sub bundles {
+	my $self = shift;
+	while ( @_ ) {
+		my $module  = shift or last;
+		my $version = shift || 0;
+		push @{ $self->{values}->{bundles} }, [ $module, $version ];
+	}
+	$self->{values}->{bundles};
+}
+
+# Resource handling
+sub resources {
+	my $self = shift;
+	while ( @_ ) {
+		my $resource = shift or last;
+		my $value    = shift or next;
+		push @{ $self->{values}->{resources} }, [ $resource, $value ];
+	}
+	$self->{values}->{resources};
+}
+
+sub repository {
+	my $self = shift;
+	$self->resources( repository => shift );
+	return 1;
+}
+
+# Aliases for build_requires that will have alternative
+# meanings in some future version of META.yml.
+sub test_requires      { shift->build_requires(@_) }
+sub install_requires   { shift->build_requires(@_) }
+
+# Aliases for installdirs options
+sub install_as_core    { $_[0]->installdirs('perl')   }
+sub install_as_cpan    { $_[0]->installdirs('site')   }
+sub install_as_site    { $_[0]->installdirs('site')   }
+sub install_as_vendor  { $_[0]->installdirs('vendor') }
+
+sub sign {
+	my $self = shift;
+	return $self->{'values'}{'sign'} if defined wantarray and ! @_;
+	$self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
+	return $self;
+}
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+		return $self;
+	}
+	$self->{values}{dynamic_config} = $_[0] ? 1 : 0;
+	return $self;
+}
+
+sub all_from {
+	my ( $self, $file ) = @_;
+
+	unless ( defined($file) ) {
+		my $name = $self->name
+			or die "all_from called with no args without setting name() first";
+		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
+		$file =~ s{.*/}{} unless -e $file;
+		die "all_from: cannot find $file from $name" unless -e $file;
+	}
+
+	# Some methods pull from POD instead of code.
+	# If there is a matching .pod, use that instead
+	my $pod = $file;
+	$pod =~ s/\.pm$/.pod/i;
+	$pod = $file unless -e $pod;
+
+	# Pull the different values
+	$self->name_from($file)         unless $self->name;
+	$self->version_from($file)      unless $self->version;
+	$self->perl_version_from($file) unless $self->perl_version;
+	$self->author_from($pod)        unless $self->author;
+	$self->license_from($pod)       unless $self->license;
+	$self->abstract_from($pod)      unless $self->abstract;
+
+	return 1;
+}
+
+sub provides {
+	my $self     = shift;
+	my $provides = ( $self->{values}{provides} ||= {} );
+	%$provides = (%$provides, @_) if @_;
+	return $provides;
+}
+
+sub auto_provides {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	unless (-e 'MANIFEST') {
+		warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+		return $self;
+	}
+	# Avoid spurious warnings as we are not checking manifest here.
+	local $SIG{__WARN__} = sub {1};
+	require ExtUtils::Manifest;
+	local *ExtUtils::Manifest::manicheck = sub { return };
+
+	require Module::Build;
+	my $build = Module::Build->new(
+		dist_name    => $self->name,
+		dist_version => $self->version,
+		license      => $self->license,
+	);
+	$self->provides( %{ $build->find_dist_packages || {} } );
+}
+
+sub feature {
+	my $self     = shift;
+	my $name     = shift;
+	my $features = ( $self->{values}{features} ||= [] );
+	my $mods;
+
+	if ( @_ == 1 and ref( $_[0] ) ) {
+		# The user used ->feature like ->features by passing in the second
+		# argument as a reference.  Accomodate for that.
+		$mods = $_[0];
+	} else {
+		$mods = \@_;
+	}
+
+	my $count = 0;
+	push @$features, (
+		$name => [
+			map {
+				ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
+			} @$mods
+		]
+	);
+
+	return @$features;
+}
+
+sub features {
+	my $self = shift;
+	while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+		$self->feature( $name, @$mods );
+	}
+	return $self->{values}->{features}
+		? @{ $self->{values}->{features} }
+		: ();
+}
+
+sub no_index {
+	my $self = shift;
+	my $type = shift;
+	push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+	return $self->{values}{no_index};
+}
+
+sub read {
+	my $self = shift;
+	$self->include_deps( 'YAML::Tiny', 0 );
+
+	require YAML::Tiny;
+	my $data = YAML::Tiny::LoadFile('META.yml');
+
+	# Call methods explicitly in case user has already set some values.
+	while ( my ( $key, $value ) = each %$data ) {
+		next unless $self->can($key);
+		if ( ref $value eq 'HASH' ) {
+			while ( my ( $module, $version ) = each %$value ) {
+				$self->can($key)->($self, $module => $version );
+			}
+		} else {
+			$self->can($key)->($self, $value);
+		}
+	}
+	return $self;
+}
+
+sub write {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	$self->admin->write_meta;
+	return $self;
+}
+
+sub version_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->abstract(
+		bless(
+			{ DISTNAME => $self->name },
+			'ExtUtils::MM_Unix'
+		)->parse_abstract($file)
+	 );
+}
+
+# Add both distribution and module name
+sub name_from {
+	my ($self, $file) = @_;
+	if (
+		Module::Install::_read($file) =~ m/
+		^ \s*
+		package \s*
+		([\w:]+)
+		\s* ;
+		/ixms
+	) {
+		my ($name, $module_name) = ($1, $1);
+		$name =~ s{::}{-}g;
+		$self->name($name);
+		unless ( $self->module_name ) {
+			$self->module_name($module_name);
+		}
+	} else {
+		die "Cannot determine name from $file\n";
+	}
+}
+
+sub perl_version_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		^
+		(?:use|require) \s*
+		v?
+		([\d_\.]+)
+		\s* ;
+		/ixms
+	) {
+		my $perl_version = $1;
+		$perl_version =~ s{_}{}g;
+		$self->perl_version($perl_version);
+	} else {
+		warn "Cannot determine perl version info from $_[0]\n";
+		return;
+	}
+}
+
+sub author_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	if ($content =~ m/
+		=head \d \s+ (?:authors?)\b \s*
+		([^\n]*)
+		|
+		=head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+		.*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+		([^\n]*)
+	/ixms) {
+		my $author = $1 || $2;
+		$author =~ s{E<lt>}{<}g;
+		$author =~ s{E<gt>}{>}g;
+		$self->author($author);
+	} else {
+		warn "Cannot determine author info from $_[0]\n";
+	}
+}
+
+sub license_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		(
+			=head \d \s+
+			(?:licen[cs]e|licensing|copyright|legal)\b
+			.*?
+		)
+		(=head\\d.*|=cut.*|)
+		\z
+	/ixms ) {
+		my $license_text = $1;
+		my @phrases      = (
+			'under the same (?:terms|license) as perl itself' => 'perl',        1,
+			'GNU public license'                              => 'gpl',         1,
+			'GNU lesser public license'                       => 'lgpl',        1,
+			'BSD license'                                     => 'bsd',         1,
+			'Artistic license'                                => 'artistic',    1,
+			'GPL'                                             => 'gpl',         1,
+			'LGPL'                                            => 'lgpl',        1,
+			'BSD'                                             => 'bsd',         1,
+			'Artistic'                                        => 'artistic',    1,
+			'MIT'                                             => 'mit',         1,
+			'proprietary'                                     => 'proprietary', 0,
+		);
+		while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
+			$pattern =~ s{\s+}{\\s+}g;
+			if ( $license_text =~ /\b$pattern\b/i ) {
+				if ( $osi and $license_text =~ /All rights reserved/i ) {
+					print "WARNING: 'All rights reserved' in copyright may invalidate Open Source license.\n";
+				}
+				$self->license($license);
+				return 1;
+			}
+		}
+	}
+
+	warn "Cannot determine license info from $_[0]\n";
+	return 'unknown';
+}
+
+sub install_script {
+	my $self = shift;
+	my $args = $self->makemaker_args;
+	my $exe  = $args->{EXE_FILES} ||= [];
+        foreach ( @_ ) {
+		if ( -f $_ ) {
+			push @$exe, $_;
+		} elsif ( -d 'script' and -f "script/$_" ) {
+			push @$exe, "script/$_";
+		} else {
+			die "Cannot find script '$_'";
+		}
+	}
+}
+
+1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
new file mode 100644
index 0000000..c97701b
--- /dev/null
+++ b/inc/Module/Install/Win32.pm
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.75';
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	die <<'END_MESSAGE' unless $rv;
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+
+}
+
+1;
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
new file mode 100644
index 0000000..e80deb8
--- /dev/null
+++ b/inc/Module/Install/WriteAll.pm
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.75';
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+sub WriteAll {
+	my $self = shift;
+	my %args = (
+		meta        => 1,
+		sign        => 0,
+		inline      => 0,
+		check_nmake => 1,
+		@_,
+	);
+
+	$self->sign(1)                if $args{sign};
+	$self->Meta->write            if $args{meta};
+	$self->admin->WriteAll(%args) if $self->is_admin;
+
+	$self->check_nmake if $args{check_nmake};
+	unless ( $self->makemaker_args->{PL_FILES} ) {
+		$self->makemaker_args( PL_FILES => {} );
+	}
+
+	if ( $args{inline} ) {
+		$self->Inline->write;
+	} else {
+		$self->Makefile->write;
+	}
+}
+
+1;
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
new file mode 100644
index 0000000..bcdfe0d
--- /dev/null
+++ b/lib/Path/Dispatcher.pm
@@ -0,0 +1,8 @@
+#!/usr/bin/env perl
+package Path::Dispatcher;
+use strict;
+use warnings;
+
+
+1;
+

commit 15355c92b36c3ebfdb616d630d8957ab18a51e06
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 17:26:25 2008 +0000

    Begin the API (upon which the sugar will be built)

diff --git a/Makefile.PL b/Makefile.PL
index 9bbf979..aa9f245 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,6 +3,9 @@ use inc::Module::Install;
 name           'Path-Dispatcher';
 all_from       'lib/Path/Dispatcher.pm';
 
+requires 'Moose';
+requires 'MooseX::AttributeHelpers';
+
 build_requires 'Test::More';
 
 WriteAll;
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index bcdfe0d..06e8557 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,8 +1,32 @@
 #!/usr/bin/env perl
 package Path::Dispatcher;
-use strict;
-use warnings;
+use Moose;
+use MooseX::AttributeHelpers;
 
+has rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef',
+    default   => sub { [] },
+    provides  => {
+        push => 'add_rule',
+    },
+);
+
+sub dispatch {
+    my $self = shift;
+
+    return sub {};
+}
+
+sub run {
+    my $self = shift;
+    my $code = $self->dispatch(@_);
+    return $code->();
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
 
 1;
 
diff --git a/t/000-compile.t b/t/000-compile.t
new file mode 100644
index 0000000..f7cd8d0
--- /dev/null
+++ b/t/000-compile.t
@@ -0,0 +1,7 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+use_ok 'Path::Dispatcher';
+
diff --git a/t/001-api.t b/t/001-api.t
new file mode 100644
index 0000000..cb45706
--- /dev/null
+++ b/t/001-api.t
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 4;
+use Path::Dispatcher;
+
+my $calls = 0;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    stage => 'on',
+    match => '*',
+    run   => sub { ++$calls },
+);
+
+is($calls, 0, "no calls to the dispatcher block yet");
+my $thunk = $dispatcher->dispatch('foo');
+is($calls, 0, "no calls to the dispatcher block yet");
+
+$thunk->();
+is($calls, 1, "made a call to the dispatcher block");
+
+$calls = 0;
+
+$dispatcher->run('foo');
+is($calls, 1, "run does all three stages");
+

commit b878b8eea3a9bb4c5d7189fd9c7e2f3a0fecffb4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 17:36:55 2008 +0000

    Reify rules

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 06e8557..6c181ff 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,16 +3,34 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
+use Path::Dispatcher::Rule;
+
+sub rule_class { 'Path::Dispatcher::Rule' }
+
 has rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',
-    isa       => 'ArrayRef',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
     default   => sub { [] },
     provides  => {
-        push => 'add_rule',
+        push => '_add_rule',
     },
 );
 
+sub add_rule {
+    my $self = shift;
+
+    my $rule;
+    if (@_ == 1 && blessed($_[0])) {
+        $rule = shift;
+    }
+    else {
+        $rule = $self->rule_class->new(@_);
+    }
+
+    $self->_add_rule($rule);
+}
+
 sub dispatch {
     my $self = shift;
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
new file mode 100644
index 0000000..c2c8678
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -0,0 +1,39 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule;
+use Moose;
+
+has stage => (
+    is       => 'rw',
+    isa      => 'Str',
+    default  => 'on',
+    required => 1,
+);
+
+has match => (
+    is       => 'rw',
+    isa      => 'Regexp',
+    required => 1,
+);
+
+has run => (
+    is       => 'rw',
+    isa      => 'CodeRef',
+    required => 1,
+);
+
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $self = shift;
+    my $args = $self->$orig(@_);
+
+    $args->{match} = qr/$args->{match}/
+        if !ref($args->{match});
+
+    return $args;
+};
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/t/001-api.t b/t/001-api.t
index cb45706..3c05754 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -9,7 +9,7 @@ my $calls = 0;
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
     stage => 'on',
-    match => '*',
+    match => 'foo',
     run   => sub { ++$calls },
 );
 

commit 9ae5730ddd307541613601f9afefea6a73d5186e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 18:42:27 2008 +0000

    Tweak rule a little, add matches and run methods

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index c2c8678..3198c1d 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -15,7 +15,7 @@ has match => (
     required => 1,
 );
 
-has run => (
+has block => (
     is       => 'rw',
     isa      => 'CodeRef',
     required => 1,
@@ -32,6 +32,20 @@ around BUILDARGS => sub {
     return $args;
 };
 
+sub matches {
+    my $self = shift;
+    my $path = shift;
+
+    return $path =~ $self->match;
+}
+
+sub run {
+    my $self = shift;
+    my $path = shift;
+
+    $self->block->($path);
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/t/001-api.t b/t/001-api.t
index 3c05754..81697aa 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -10,7 +10,7 @@ my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
     stage => 'on',
     match => 'foo',
-    run   => sub { ++$calls },
+    block => sub { ++$calls },
 );
 
 is($calls, 0, "no calls to the dispatcher block yet");

commit 63fe5800c5c33f1a84ffa5a771ca9539a9afa1aa
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 18:42:31 2008 +0000

    Flesh out the dispatch code

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 6c181ff..780755b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -7,13 +7,14 @@ use Path::Dispatcher::Rule;
 
 sub rule_class { 'Path::Dispatcher::Rule' }
 
-has rules => (
+has _rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',
     isa       => 'ArrayRef[Path::Dispatcher::Rule]',
     default   => sub { [] },
     provides  => {
-        push => '_add_rule',
+        push     => '_add_rule',
+        elements => 'rules',
     },
 );
 
@@ -33,8 +34,34 @@ sub add_rule {
 
 sub dispatch {
     my $self = shift;
+    my $path = shift;
 
-    return sub {};
+    my @rules;
+
+    for my $rule ($self->rules) {
+        if ($rule->matches($path)) {
+            push @rules, $rule;
+        }
+    }
+
+    return $self->build_runner(
+        path  => $path,
+        rules => \@rules,
+    );
+}
+
+sub build_runner {
+    my $self = shift;
+    my %args = @_;
+
+    my $path  = $args{path};
+    my $rules = $args{rules};
+
+    return sub {
+        for my $rule (@$rules) {
+            $rule->run($path);
+        }
+    };
 }
 
 sub run {

commit b6f1824ac68f7f0be0400ecf2aef91e3dcfb33bf
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:17:54 2008 +0000

    Better test Jifty::Dispatcher compat - each rule coderef receives no arguments

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 3198c1d..d6af9fb 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -43,7 +43,7 @@ sub run {
     my $self = shift;
     my $path = shift;
 
-    $self->block->($path);
+    $self->block->();
 }
 
 __PACKAGE__->meta->make_immutable;
diff --git a/t/001-api.t b/t/001-api.t
index 81697aa..d654059 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -4,24 +4,22 @@ use warnings;
 use Test::More tests => 4;
 use Path::Dispatcher;
 
-my $calls = 0;
+my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    stage => 'on',
     match => 'foo',
-    block => sub { ++$calls },
+    block => sub { push @calls, [@_] },
 );
 
-is($calls, 0, "no calls to the dispatcher block yet");
+is_deeply([splice @calls], [], "no calls to the rule block yet");
+
 my $thunk = $dispatcher->dispatch('foo');
-is($calls, 0, "no calls to the dispatcher block yet");
+is_deeply([splice @calls], [], "no calls to the rule block yet");
 
 $thunk->();
-is($calls, 1, "made a call to the dispatcher block");
-
-$calls = 0;
+is_deeply([splice @calls], [ [] ], "finally invoked the rule block");
 
 $dispatcher->run('foo');
-is($calls, 1, "run does all three stages");
+is_deeply([splice @calls], [ [] ], "invoked the rule block on 'run'");
 

commit cec06a4139b9ddfe80e116f6d53a917942297f52
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:45:30 2008 +0000

    Refactor the dispatcher rule, solidify the "match" method

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index d6af9fb..251209f 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -9,7 +9,7 @@ has stage => (
     required => 1,
 );
 
-has match => (
+has regex => (
     is       => 'rw',
     isa      => 'Regexp',
     required => 1,
@@ -21,22 +21,13 @@ has block => (
     required => 1,
 );
 
-around BUILDARGS => sub {
-    my $orig = shift;
-    my $self = shift;
-    my $args = $self->$orig(@_);
-
-    $args->{match} = qr/$args->{match}/
-        if !ref($args->{match});
-
-    return $args;
-};
-
-sub matches {
+sub match {
     my $self = shift;
     my $path = shift;
 
-    return $path =~ $self->match;
+    return unless $path =~ $self->regex;
+
+    return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ]
 }
 
 sub run {
diff --git a/t/001-api.t b/t/001-api.t
index d654059..03dbdf7 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -8,7 +8,7 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    match => 'foo',
+    regex => qr/foo/,
     block => sub { push @calls, [@_] },
 );
 

commit 873138840ff13d485c57e7214cd19f7bf00e404f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:45:50 2008 +0000

    Add more tests

diff --git a/t/001-api.t b/t/001-api.t
index 03dbdf7..2ee29c1 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 4;
+use Test::More tests => 8;
 use Path::Dispatcher;
 
 my @calls;
@@ -23,3 +23,19 @@ is_deeply([splice @calls], [ [] ], "finally invoked the rule block");
 $dispatcher->run('foo');
 is_deeply([splice @calls], [ [] ], "invoked the rule block on 'run'");
 
+$dispatcher->add_rule(
+    regex => qr/(bar)/,
+    block => sub { push @calls, [$1, $2] },
+);
+
+is_deeply([splice @calls], [], "no calls to the rule block yet");
+
+$thunk = $dispatcher->dispatch('bar');
+is_deeply([splice @calls], [], "no calls to the rule block yet");
+
+$thunk->();
+is_deeply([splice @calls], [ ['bar', undef] ], "finally invoked the rule block");
+
+$dispatcher->run('bar');
+is_deeply([splice @calls], [ ['bar', undef] ], "invoked the rule block on 'run'");
+
diff --git a/t/002-rule.t b/t/002-rule.t
new file mode 100644
index 0000000..3499ce7
--- /dev/null
+++ b/t/002-rule.t
@@ -0,0 +1,13 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher::Rule;
+
+my $rule = Path::Dispatcher::Rule->new(
+    regex => qr/^(..)(..)/,
+    block => sub {},
+);
+
+is_deeply([$rule->match('foobar')], [['fo', 'ob']]);
+

commit a3e8de606df8d9cb345da2c684aab4d48af113cd
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:45:56 2008 +0000

    Keep track of $1 and friends, and populate them at the right time

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 780755b..6846fc7 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -36,17 +36,21 @@ sub dispatch {
     my $self = shift;
     my $path = shift;
 
-    my @rules;
+    my @matches;
 
     for my $rule ($self->rules) {
-        if ($rule->matches($path)) {
-            push @rules, $rule;
-        }
+        my $vars = $rule->match($path)
+            or next;
+
+        push @matches, {
+            rule => $rule,
+            vars => $vars,
+        };
     }
 
     return $self->build_runner(
-        path  => $path,
-        rules => \@rules,
+        path    => $path,
+        matches => \@matches,
     );
 }
 
@@ -54,16 +58,34 @@ sub build_runner {
     my $self = shift;
     my %args = @_;
 
-    my $path  = $args{path};
-    my $rules = $args{rules};
+    my $path    = $args{path};
+    my $matches = $args{matches};
 
     return sub {
-        for my $rule (@$rules) {
-            $rule->run($path);
+        for my $match (@$matches) {
+            $self->run_with_number_vars(
+                sub { $match->{rule}->run($path) },
+                @{ $match->{vars} },
+            );
         }
     };
 }
 
+sub run_with_number_vars {
+    my $self = shift;
+    my $code = shift;
+
+    # we don't have direct write access to $1 and friends, so we have to
+    # do this little hack. the only way we can update $1 is by matching
+    # against a regex (5.10 fixes that)..
+    my $re = join '', map { "(\Q$_\E)" } @_;
+    my $str = join '', @_;
+    $str =~ $re
+        or die "Unable to match '$str' against a copy of itself!";
+
+    $code->();
+}
+
 sub run {
     my $self = shift;
     my $code = $self->dispatch(@_);

commit 23065d82040d96a4aca9762aae0ba1f3067dd788
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:46:01 2008 +0000

    More rule tests

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 251209f..afc9cdd 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -27,6 +27,7 @@ sub match {
 
     return unless $path =~ $self->regex;
 
+    # return [$1, $2, $3, ...]
     return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ]
 }
 
diff --git a/t/002-rule.t b/t/002-rule.t
index 3499ce7..712d680 100644
--- a/t/002-rule.t
+++ b/t/002-rule.t
@@ -1,13 +1,36 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 4;
 use Path::Dispatcher::Rule;
 
+my @calls;
+
 my $rule = Path::Dispatcher::Rule->new(
     regex => qr/^(..)(..)/,
-    block => sub {},
+    block => sub {
+        push @calls, {
+            vars => [$1, $2, $3],
+            args => [@_],
+        }
+    },
 );
 
 is_deeply([$rule->match('foobar')], [['fo', 'ob']]);
+is_deeply([splice @calls], [], "block not called on match");
+
+$rule->run;
+is_deeply([splice @calls], [{
+    vars => [undef, undef, undef],
+    args => [],
+}], "block called on ->run");
+
+# make sure ->run grabs $1
+"bah" =~ /^(\w+)/;
+
+$rule->run;
+is_deeply([splice @calls], [{
+    vars => ["bah", undef, undef],
+    args => [],
+}], "block called on ->run");
 

commit 53034a7a88b806d33f11e28602507057f3e04aee
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 19:48:01 2008 +0000

    Make sure that the dispatch thunk sets $1 etc properly

diff --git a/t/001-api.t b/t/001-api.t
index 2ee29c1..7a351ce 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 8;
+use Test::More tests => 9;
 use Path::Dispatcher;
 
 my @calls;
@@ -39,3 +39,8 @@ is_deeply([splice @calls], [ ['bar', undef] ], "finally invoked the rule block")
 $dispatcher->run('bar');
 is_deeply([splice @calls], [ ['bar', undef] ], "invoked the rule block on 'run'");
 
+"foo" =~ /foo/;
+
+$thunk->();
+is_deeply([splice @calls], [ ['bar', undef] ], "invoked the rule block on 'run', makes sure \$1 etc are still correctly set");
+

commit 22382323b65927485204e154b0c15f4a3baf8f52
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 20:02:35 2008 +0000

    Return undef when dispatch 404s, tests for it

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 6846fc7..69e057d 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -22,9 +22,12 @@ sub add_rule {
     my $self = shift;
 
     my $rule;
+
+    # they pass in an already instantiated rule..
     if (@_ == 1 && blessed($_[0])) {
         $rule = shift;
     }
+    # or they pass in args to create a rule
     else {
         $rule = $self->rule_class->new(@_);
     }
@@ -48,6 +51,8 @@ sub dispatch {
         };
     }
 
+    return if !@matches;
+
     return $self->build_runner(
         path    => $path,
         matches => \@matches,
@@ -89,6 +94,7 @@ sub run_with_number_vars {
 sub run {
     my $self = shift;
     my $code = $self->dispatch(@_);
+
     return $code->();
 }
 
diff --git a/t/003-404.t b/t/003-404.t
new file mode 100644
index 0000000..23674ad
--- /dev/null
+++ b/t/003-404.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    regex => qr/foo/,
+    block => sub { push @calls, [@_] },
+);
+
+my $thunk = $dispatcher->dispatch('bar');
+is_deeply([splice @calls], [], "no calls to the rule block yet");
+
+is($thunk, undef, "no match, no coderef");
+

commit e241af0be8c2525f050309237f29b0abd98b0c12
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 20:09:34 2008 +0000

    Run rules in stages

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 69e057d..54ca234 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -6,6 +6,7 @@ use MooseX::AttributeHelpers;
 use Path::Dispatcher::Rule;
 
 sub rule_class { 'Path::Dispatcher::Rule' }
+sub stages { qw/before on after/ }
 
 has _rules => (
     metaclass => 'Collection::Array',
@@ -40,15 +41,25 @@ sub dispatch {
     my $path = shift;
 
     my @matches;
+    my %rules_for_stage;
 
-    for my $rule ($self->rules) {
-        my $vars = $rule->match($path)
-            or next;
+    push @{ $rules_for_stage{$_->stage} }, $_
+        for $self->rules;
 
-        push @matches, {
-            rule => $rule,
-            vars => $vars,
-        };
+    for my $stage ($self->stages) {
+        $self->begin_stage($stage);
+
+        for my $rule (@{ $rules_for_stage{$stage} || [] }) {
+            my $vars = $rule->match($path)
+                or next;
+
+            push @matches, {
+                rule => $rule,
+                vars => $vars,
+            };
+        }
+
+        $self->end_stage($stage);
     }
 
     return if !@matches;
@@ -98,6 +109,9 @@ sub run {
     return $code->();
 }
 
+sub begin_stage {}
+sub end_stage {}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/t/004-stages.t b/t/004-stages.t
new file mode 100644
index 0000000..5138fc0
--- /dev/null
+++ b/t/004-stages.t
@@ -0,0 +1,20 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+for my $stage (qw/after on before/) {
+    $dispatcher->add_rule(
+        stage => $stage,
+        regex => qr/foo/,
+        block => sub { push @calls, $stage },
+    );
+}
+
+$dispatcher->run('foo');
+is_deeply(\@calls, ['before', 'on', 'after']);
+

commit cae2192cfb8966f43199d19f4901f4346faa1b41
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 20:12:46 2008 +0000

    Pass the current match state to begin/end stage methods

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 54ca234..291e41a 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -47,7 +47,7 @@ sub dispatch {
         for $self->rules;
 
     for my $stage ($self->stages) {
-        $self->begin_stage($stage);
+        $self->begin_stage($stage, \@matches);
 
         for my $rule (@{ $rules_for_stage{$stage} || [] }) {
             my $vars = $rule->match($path)
@@ -59,7 +59,7 @@ sub dispatch {
             };
         }
 
-        $self->end_stage($stage);
+        $self->end_stage($stage, \@matches);
     }
 
     return if !@matches;

commit 47ea7e5cc36adaf1159bc107e2ecd4a44007d4a9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 20:57:33 2008 +0000

    Make attributes read-only for sanity/optimization purposes

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index afc9cdd..37612fe 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -3,20 +3,20 @@ package Path::Dispatcher::Rule;
 use Moose;
 
 has stage => (
-    is       => 'rw',
+    is       => 'ro',
     isa      => 'Str',
     default  => 'on',
     required => 1,
 );
 
 has regex => (
-    is       => 'rw',
+    is       => 'ro',
     isa      => 'Regexp',
     required => 1,
 );
 
 has block => (
-    is       => 'rw',
+    is       => 'ro',
     isa      => 'CodeRef',
     required => 1,
 );

commit a139a610ed979471bfa899c9d21f860655c4e28d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 20:57:39 2008 +0000

    Allow rules to be "fallthrough" or not; by default, "on" rules are not fallthrough, others are

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 291e41a..d7b67b7 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -57,6 +57,8 @@ sub dispatch {
                 rule => $rule,
                 vars => $vars,
             };
+
+            last if !$rule->fallthrough;
         }
 
         $self->end_stage($stage, \@matches);
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 37612fe..7076597 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -21,6 +21,16 @@ has block => (
     required => 1,
 );
 
+has fallthrough => (
+    is      => 'ro',
+    isa     => 'Bool',
+    lazy    => 1,
+    default => sub {
+        my $self = shift;
+        $self->stage eq 'on' ? 0 : 1;
+    },
+);
+
 sub match {
     my $self = shift;
     my $path = shift;
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
new file mode 100644
index 0000000..6fdc102
--- /dev/null
+++ b/t/005-multi-rule.t
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+for my $stage (qw/before on after/) {
+    for my $number (qw/first second/) {
+        $dispatcher->add_rule(
+            stage => $stage,
+            regex => qr/foo/,
+            block => sub { push @calls, "$stage: $number" },
+        );
+    }
+}
+
+$dispatcher->run('foo');
+is_deeply(\@calls, [
+    'before: first',
+    'before: second',
+    'on: first',
+    'after: first',
+    'after: second',
+]);
+

commit 8b74056e4b136216b86f0bb12a9852a5f6375f62
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 21:16:51 2008 +0000

    (Failing) tests for aborting the dispatch

diff --git a/t/006-abort.t b/t/006-abort.t
new file mode 100644
index 0000000..68bedae
--- /dev/null
+++ b/t/006-abort.t
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 6;
+use Test::Exception;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    regex => qr/foo/,
+    block => sub {
+        push @calls, "on";
+        die "Patch::Dispatcher abort\n";
+    },
+);
+
+$dispatcher->add_rule(
+    stage => 'after',
+    regex => qr/foo/,
+    block => sub {
+        push @calls, "after";
+    },
+);
+
+my $thunk;
+lives_ok {
+    $thunk = $dispatcher->dispatch('foo');
+};
+is_deeply([splice @calls], [], "no blocks called yet of course");
+
+lives_ok {
+    $thunk->();
+};
+is_deeply([splice @calls], ['on'], "correctly aborted the entire dispatch");
+
+$dispatcher->add_rule(
+    regex => qr/bar/,
+    block => sub {
+        push @calls, "bar: before";
+        my $x = {}->();
+        push @calls, "bar: after";
+    },
+);
+
+throws_ok {
+    $dispatcher->run('bar');
+} qr/Not a CODE reference/;
+
+is_deeply([splice @calls], ['bar: before'], "regular dies pass through");
+

commit 564dc36bb3eaf06fb39bd766b6a567f100b16429
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Jul 29 21:22:14 2008 +0000

    Implement aborting the dispatch by die-ing with a special value

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index d7b67b7..22059e7 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -80,12 +80,17 @@ sub build_runner {
     my $matches = $args{matches};
 
     return sub {
-        for my $match (@$matches) {
-            $self->run_with_number_vars(
-                sub { $match->{rule}->run($path) },
-                @{ $match->{vars} },
-            );
-        }
+        eval {
+            local $SIG{__DIE__} = 'DEFAULT';
+            for my $match (@$matches) {
+                $self->run_with_number_vars(
+                    sub { $match->{rule}->run($path) },
+                    @{ $match->{vars} },
+                );
+            }
+        };
+
+        die $@ if $@ && $@ !~ /^Patch::Dispatcher abort\n/;
     };
 }
 

commit 95cd37848c1c59722207907ec95846938c392075
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 16:19:52 2008 +0000

    Support for arbitrary dispatch matching with coderefs (so we can support Jifty::Dispatcher's "when" rule type)

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 22059e7..b1c665b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -54,8 +54,8 @@ sub dispatch {
                 or next;
 
             push @matches, {
-                rule => $rule,
-                vars => $vars,
+                rule   => $rule,
+                result => $vars,
             };
 
             last if !$rule->fallthrough;
@@ -83,10 +83,16 @@ sub build_runner {
         eval {
             local $SIG{__DIE__} = 'DEFAULT';
             for my $match (@$matches) {
-                $self->run_with_number_vars(
-                    sub { $match->{rule}->run($path) },
-                    @{ $match->{vars} },
-                );
+                # if we need to set $1, $2..
+                if (ref($match->{result}) eq 'ARRAY') {
+                    $self->run_with_number_vars(
+                        sub { $match->{rule}->run($path) },
+                        @{ $match->{result} },
+                    );
+                }
+                else {
+                    $match->{rule}->run($path);
+                }
             }
         };
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 7076597..4a7b70b 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -9,9 +9,9 @@ has stage => (
     required => 1,
 );
 
-has regex => (
+has matcher => (
     is       => 'ro',
-    isa      => 'Regexp',
+    isa      => 'CodeRef',
     required => 1,
 );
 
@@ -31,14 +31,53 @@ has fallthrough => (
     },
 );
 
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $self = shift;
+    my $args = $self->$orig(@_);
+
+    if (!$args->{matcher} && $args->{regex}) {
+        $args->{matcher} = $self->build_regex_matcher(delete $args->{regex});
+    }
+
+    return $args;
+};
+
+sub build_regex_matcher {
+    my $self = shift;
+    my $re   = shift;
+
+    # compile the regex immediately, instead of each match
+    $re = qr/$re/;
+
+    return sub {
+        return unless $_ =~ $re;
+
+        my $path = $_;
+        return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ];
+    }
+}
+
 sub match {
     my $self = shift;
     my $path = shift;
 
-    return unless $path =~ $self->regex;
+    local $_ = $path;
+    my $result = $self->matcher->();
+    return unless $result;
+
+    # make sure that the returned values are PLAIN STRINGS
+    # later we will stick them into a regular expression to populate $1 etc
+    # which will blow up later!
+
+    if (ref($result) eq 'ARRAY') {
+        for (@$result) {
+            die "Invalid result '$_', results must be plain strings"
+                if ref($_);
+        }
+    }
 
-    # return [$1, $2, $3, ...]
-    return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ]
+    return $result;
 }
 
 sub run {
diff --git a/t/007-coderef-matcher.t b/t/007-coderef-matcher.t
new file mode 100644
index 0000000..2918c46
--- /dev/null
+++ b/t/007-coderef-matcher.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my (@matches, @calls);
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    matcher => sub { push @matches, $_; length > 5 },
+    block   => sub { push @calls, [@_] },
+);
+
+$dispatcher->run('foobar');
+
+is_deeply([splice @matches], ['foobar']);
+is_deeply([splice @calls], [ [] ]);
+

commit 09071636e6e716fc3dcda7e9563dce6b619f85ab
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 18:41:02 2008 +0000

    Begin Path::Dispatcher::Declarative

diff --git a/Makefile.PL b/Makefile.PL
index aa9f245..b628a97 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -5,6 +5,7 @@ all_from       'lib/Path/Dispatcher.pm';
 
 requires 'Moose';
 requires 'MooseX::AttributeHelpers';
+requires 'Sub::Exporter';
 
 build_requires 'Test::More';
 
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
new file mode 100644
index 0000000..51047c6
--- /dev/null
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Declarative;
+use strict;
+use warnings;
+use Sub::Exporter;
+
+my $exporter = Sub::Exporter::build_exporter({
+    exports => {
+    },
+    groups => {
+        default => [':all'],
+    },
+
+});
+
+sub import {
+    my $self = shift;
+    my $pkg  = caller;
+    my @args = grep { !/^-[Bb]ase/ } @_;
+
+    # they must have specified '-base' if there are no args
+    if (@args != @_) {
+        no strict 'refs';
+        push @{ $pkg . '::ISA' }, $self
+    }
+
+    $exporter->($self, @args);
+}
+
+1;
+
diff --git a/t/100-declarative.t b/t/100-declarative.t
new file mode 100644
index 0000000..6d2adcc
--- /dev/null
+++ b/t/100-declarative.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+do {
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+};
+
+ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative -base sets up ISA");
+
+do {
+    package MyApp::Dispatcher::NoBase;
+    use Path::Dispatcher::Declarative;
+};
+
+ok(!MyApp::Dispatcher::NoBase->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative without -base does not set up ISA");
+

commit 97c353f70aa37fa794915c79833df97912e3fc9d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 19:39:21 2008 +0000

    Figure out how sugar will be distributed

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 51047c6..f2cb5fe 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -3,14 +3,13 @@ package Path::Dispatcher::Declarative;
 use strict;
 use warnings;
 use Sub::Exporter;
+use Path::Dispatcher;
 
 my $exporter = Sub::Exporter::build_exporter({
-    exports => {
-    },
+    into_level => 1,
     groups => {
-        default => [':all'],
+        default => \&build_sugar,
     },
-
 });
 
 sub import {
@@ -18,14 +17,26 @@ sub import {
     my $pkg  = caller;
     my @args = grep { !/^-[Bb]ase/ } @_;
 
-    # they must have specified '-base' if there are no args
+    # they must have specified '-base' if there are a different number of args
     if (@args != @_) {
         no strict 'refs';
-        push @{ $pkg . '::ISA' }, $self
+        push @{ $pkg . '::ISA' }, $self;
     }
 
     $exporter->($self, @args);
 }
 
+sub build_sugar {
+    my ($class, $group, $arg) = @_;
+
+    my $dispatcher = Path::Dispatcher->new;
+
+    return {
+        dispatcher => sub { $dispatcher },
+        dispatch   => sub { $dispatcher->dispatch(@_) },
+        run        => sub { $dispatcher->run(@_) },
+    };
+}
+
 1;
 
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 6d2adcc..a07dddc 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 2;
+use Test::More tests => 4;
 
 do {
     package MyApp::Dispatcher;
@@ -9,6 +9,7 @@ do {
 };
 
 ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative -base sets up ISA");
+can_ok('MyApp::Dispatcher', qw/dispatcher dispatch run/);
 
 do {
     package MyApp::Dispatcher::NoBase;
@@ -16,4 +17,5 @@ do {
 };
 
 ok(!MyApp::Dispatcher::NoBase->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative without -base does not set up ISA");
+can_ok('MyApp::Dispatcher::NoBase', qw/dispatcher dispatch run/);
 

commit 2cb8eec3c52f6aa18331c8bd7ca4f58d38c31b77
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 19:55:14 2008 +0000

    Add "on", more tests

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index f2cb5fe..640ccdb 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -33,8 +33,20 @@ sub build_sugar {
 
     return {
         dispatcher => sub { $dispatcher },
-        dispatch   => sub { $dispatcher->dispatch(@_) },
-        run        => sub { $dispatcher->run(@_) },
+        dispatch   => sub {
+            shift; # don't need $self
+            $dispatcher->dispatch(@_);
+        },
+        run => sub {
+            shift; # don't need $self
+            $dispatcher->run(@_);
+        },
+        on => sub {
+            $dispatcher->add_rule(
+                regex => $_[0],
+                block => $_[1],
+            );
+        },
     };
 }
 
diff --git a/t/100-declarative.t b/t/100-declarative.t
index a07dddc..74c303d 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,21 +1,41 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 4;
+use Test::More tests => 5;
 
-do {
-    package MyApp::Dispatcher;
-    use Path::Dispatcher::Declarative -base;
-};
+my @calls;
 
-ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative -base sets up ISA");
-can_ok('MyApp::Dispatcher', qw/dispatcher dispatch run/);
+for my $use_base (0, 1) {
+    my $dispatcher = $use_base ? 'MyApp::Dispatcher' : 'MyApp::DispatcherBase';
 
-do {
-    package MyApp::Dispatcher::NoBase;
-    use Path::Dispatcher::Declarative;
-};
+    # duplicated code is worse than eval!
+    my $code = "
+        package $dispatcher;
+    ";
 
-ok(!MyApp::Dispatcher::NoBase->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative without -base does not set up ISA");
-can_ok('MyApp::Dispatcher::NoBase', qw/dispatcher dispatch run/);
+    $code .= 'use Path::Dispatcher::Declarative';
+    $code .= ' -base' if $use_base;
+    $code .= ';';
+
+    $code .= '
+        on qr/(b)(ar)(.*)/ => sub {
+            push @calls, [$1, $2, $3];
+        };
+    ';
+
+    eval $code;
+
+    if ($use_base) {
+        ok($dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative -base sets up ISA");
+    }
+    else {
+        ok(!$dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative does NOT set up ISA");
+    }
+
+    can_ok($dispatcher => qw/dispatcher dispatch run/);
+    $dispatcher->run('foobarbaz');
+    is_deeply([splice @calls], [
+        [ 'b', 'ar', 'baz' ],
+    ]);
+}
 

commit 9f5d2286632ffedc933dfe303d5732f06a0e5762
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 20:14:28 2008 +0000

    Add (failing) tests for layering dispatcher rules across subclasses

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 640ccdb..e2f6ff0 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -47,6 +47,20 @@ sub build_sugar {
                 block => $_[1],
             );
         },
+        before => sub {
+            $dispatcher->add_rule(
+                stage => 'before',
+                regex => $_[0],
+                block => $_[1],
+            );
+        },
+        after => sub {
+            $dispatcher->add_rule(
+                stage => 'after',
+                regex => $_[0],
+                block => $_[1],
+            );
+        },
     };
 }
 
diff --git a/t/101-subclass.t b/t/101-subclass.t
new file mode 100644
index 0000000..50088d7
--- /dev/null
+++ b/t/101-subclass.t
@@ -0,0 +1,42 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 3;
+use lib 't/lib';
+use Path::Dispatcher::Test::App;
+
+our @calls;
+
+Path::Dispatcher::Test::Framework->run('foo');
+is_deeply([splice @calls], [
+    'framework before foo',
+    'framework on foo',
+    'framework after foo',
+]);
+
+TODO: {
+    local $TODO = "no layering yet :(";
+    Path::Dispatcher::Test::App->run('foo');
+    is_deeply([splice @calls], [
+        'app before foo',
+        'framework before foo',
+        'framework on foo',
+        'framework after foo',
+        'app after foo',
+    ]);
+}
+
+Path::Dispatcher::Test::App->dispatcher->add_rule(
+    regex => qr/foo/,
+    block => sub {
+        push @calls, 'app on foo';
+    },
+);
+
+Path::Dispatcher::Test::App->run('foo');
+is_deeply([splice @calls], [
+    'app before foo',
+    'app on foo',
+    'app after foo',
+]);
+
diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
new file mode 100644
index 0000000..73af2b8
--- /dev/null
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Test::App;
+use strict;
+use warnings;
+use Path::Dispatcher::Test::Framework -base;
+
+before qr/foo/ => sub {
+    push @main::calls, 'app before foo';
+};
+
+after qr/foo/ => sub {
+    push @main::calls, 'app after foo';
+};
+
+1;
+
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
new file mode 100644
index 0000000..e7d31b1
--- /dev/null
+++ b/t/lib/Path/Dispatcher/Test/Framework.pm
@@ -0,0 +1,20 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Test::Framework;
+use strict;
+use warnings;
+use Path::Dispatcher::Declarative -base;
+
+before qr/foo/ => sub {
+    push @main::calls, 'framework before foo';
+};
+
+on qr/foo/ => sub {
+    push @main::calls, 'framework on foo';
+};
+
+after qr/foo/ => sub {
+    push @main::calls, 'framework after foo';
+};
+
+1;
+

commit b9d3943aa118c7d9dec91c5c941f2dfce7b83349
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 20:26:34 2008 +0000

    Add failing tests for having a super dispatcher

diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
new file mode 100644
index 0000000..74162d1
--- /dev/null
+++ b/t/008-super-dispatcher.t
@@ -0,0 +1,58 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 3;
+use Path::Dispatcher;
+
+my @calls;
+
+my $super_dispatcher = Path::Dispatcher->new;
+my $sub_dispatcher   = Path::Dispatcher->new(
+    super_dispatcher => $super_dispatcher,
+);
+
+for my $stage (qw/before on after/) {
+    $super_dispatcher->add_rule(
+        stage => $stage,
+        regex => qr/foo/,
+        block => sub { push @calls, "super $stage" },
+    );
+}
+
+for my $stage (qw/before after/) {
+    $sub_dispatcher->add_rule(
+        stage => $stage,
+        regex => qr/foo/,
+        block => sub { push @calls, "sub $stage" },
+    );
+}
+
+$super_dispatcher->run('foo');
+is_deeply([splice @calls], [
+    'super before',
+    'super on',
+    'super after',
+]);
+
+$sub_dispatcher->run('foo');
+is_deeply([splice @calls], [
+    'sub before',
+    'super before',
+    'super on',
+    'super after',
+    'sub after',
+]);
+
+$sub_dispatcher->add_rule(
+    stage => 'on',
+    regex => qr/foo/,
+    block => sub { push @calls, "sub on" },
+);
+
+$sub_dispatcher->run('foo');
+is_deeply([splice @calls], [
+    'sub before',
+    'sub on',
+    'sub after',
+]);
+

commit c08e23fdde45cb064b95dd6d97d7c4b1f26bf6e1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 20:39:14 2008 +0000

    Support (and more tests for) "super" dispatchers

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index b1c665b..208610e 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -19,6 +19,12 @@ has _rules => (
     },
 );
 
+has super_dispatcher => (
+    is        => 'rw',
+    isa       => 'Path::Dispatcher',
+    predicate => 'has_super_dispatcher',
+);
+
 sub add_rule {
     my $self = shift;
 
@@ -48,11 +54,14 @@ sub dispatch {
 
     for my $stage ($self->stages) {
         $self->begin_stage($stage, \@matches);
+        my $stage_matches = 0;
 
         for my $rule (@{ $rules_for_stage{$stage} || [] }) {
             my $vars = $rule->match($path)
                 or next;
 
+            ++$stage_matches;
+
             push @matches, {
                 rule   => $rule,
                 result => $vars,
@@ -61,6 +70,14 @@ sub dispatch {
             last if !$rule->fallthrough;
         }
 
+        my $defer = $stage_matches == 0
+                 && $self->has_super_dispatcher
+                 && $self->defer_to_super_dispatcher($stage);
+
+        if ($defer) {
+            push @matches, $self->super_dispatcher->dispatch($path);
+        }
+
         $self->end_stage($stage, \@matches);
     }
 
@@ -83,6 +100,11 @@ sub build_runner {
         eval {
             local $SIG{__DIE__} = 'DEFAULT';
             for my $match (@$matches) {
+                if (ref($match) eq 'CODE') {
+                    $match->();
+                    next;
+                }
+
                 # if we need to set $1, $2..
                 if (ref($match->{result}) eq 'ARRAY') {
                     $self->run_with_number_vars(
@@ -125,6 +147,14 @@ sub run {
 sub begin_stage {}
 sub end_stage {}
 
+sub defer_to_super_dispatcher {
+    my $self = shift;
+    my $stage = shift;
+
+    return 1 if $stage eq 'on';
+    return 0;
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index 74162d1..e504d55 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 3;
+use Test::More tests => 6;
 use Path::Dispatcher;
 
 my @calls;
@@ -11,6 +11,10 @@ my $sub_dispatcher   = Path::Dispatcher->new(
     super_dispatcher => $super_dispatcher,
 );
 
+ok(!$super_dispatcher->has_super_dispatcher, "no super dispatcher by default");
+ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
+is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");
+
 for my $stage (qw/before on after/) {
     $super_dispatcher->add_rule(
         stage => $stage,

commit f2fdadb2510c7f0d01cb8010cf39b6cbde82374d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 20:45:49 2008 +0000

    Small refactor to give defer_to_super_dispatcher more control

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 208610e..d4ec5ee 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -54,15 +54,13 @@ sub dispatch {
 
     for my $stage ($self->stages) {
         $self->begin_stage($stage, \@matches);
-        my $stage_matches = 0;
 
         for my $rule (@{ $rules_for_stage{$stage} || [] }) {
             my $vars = $rule->match($path)
                 or next;
 
-            ++$stage_matches;
-
             push @matches, {
+                stage  => $stage,
                 rule   => $rule,
                 result => $vars,
             };
@@ -70,11 +68,7 @@ sub dispatch {
             last if !$rule->fallthrough;
         }
 
-        my $defer = $stage_matches == 0
-                 && $self->has_super_dispatcher
-                 && $self->defer_to_super_dispatcher($stage);
-
-        if ($defer) {
+        if ($self->defer_to_super_dispatcher($stage, \@matches)) {
             push @matches, $self->super_dispatcher->dispatch($path);
         }
 
@@ -150,9 +144,21 @@ sub end_stage {}
 sub defer_to_super_dispatcher {
     my $self = shift;
     my $stage = shift;
+    my $matches = shift;
+
+    return 0 if !$self->has_super_dispatcher;
+
+    # we only defer in the "on" stage.. this is sort of yucky, maybe we want
+    # implicit "before/after" every stage
+    return 0 unless $stage eq 'on';
+
+    # do not defer if we have any matches for this stage
+    return 0 if grep { $_->{stage} eq $stage }
+                grep { ref($_) eq 'HASH' }
+                @$matches;
 
-    return 1 if $stage eq 'on';
-    return 0;
+    # okay, let dad have at it!
+    return 1;
 }
 
 __PACKAGE__->meta->make_immutable;

commit c5cd0314f8c1dbe12a1353381b004f1591d8ad1e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jul 30 20:46:07 2008 +0000

    Support for super dispatchers in the sugar layer

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index e2f6ff0..0721da4 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -31,6 +31,11 @@ sub build_sugar {
 
     my $dispatcher = Path::Dispatcher->new;
 
+    # if this is a subclass, then we want to set up a super dispatcher
+    if ($class ne __PACKAGE__) {
+        $dispatcher->super_dispatcher($class->dispatcher);
+    }
+
     return {
         dispatcher => sub { $dispatcher },
         dispatch   => sub {
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 74c303d..32ef1fb 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 5;
+use Test::More tests => 6;
 
 my @calls;
 
diff --git a/t/101-subclass.t b/t/101-subclass.t
index 50088d7..547d53f 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -14,17 +14,14 @@ is_deeply([splice @calls], [
     'framework after foo',
 ]);
 
-TODO: {
-    local $TODO = "no layering yet :(";
-    Path::Dispatcher::Test::App->run('foo');
-    is_deeply([splice @calls], [
-        'app before foo',
-        'framework before foo',
-        'framework on foo',
-        'framework after foo',
-        'app after foo',
-    ]);
-}
+Path::Dispatcher::Test::App->run('foo');
+is_deeply([splice @calls], [
+    'app before foo',
+    'framework before foo',
+    'framework on foo',
+    'framework after foo',
+    'app after foo',
+]);
 
 Path::Dispatcher::Test::App->dispatcher->add_rule(
     regex => qr/foo/,

commit 5a1b3f96a9d5edd6435e341225ae92889c8a188e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Fri Aug 1 17:56:12 2008 +0000

    Support for named dispatchers

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index d4ec5ee..faf4f47 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -25,6 +25,17 @@ has super_dispatcher => (
     predicate => 'has_super_dispatcher',
 );
 
+has name => (
+    is      => 'rw',
+    isa     => 'Str',
+    default => do {
+        my $i = 0;
+        sub {
+            join '-', __PACKAGE__, ++$i;
+        },
+    },
+);
+
 sub add_rule {
     my $self = shift;
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index e504d55..174e400 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 6;
+use Test::More tests => 7;
 use Path::Dispatcher;
 
 my @calls;
@@ -11,6 +11,8 @@ my $sub_dispatcher   = Path::Dispatcher->new(
     super_dispatcher => $super_dispatcher,
 );
 
+isnt($super_dispatcher->name, $sub_dispatcher->name, "two dispatchers have separate names");
+
 ok(!$super_dispatcher->has_super_dispatcher, "no super dispatcher by default");
 ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
 is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");

commit 83b50fd054a5ed4848fd47815832b9098b29fc9b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Fri Aug 1 17:57:32 2008 +0000

    Declarative dispatchers take their name from the package into which they're installed

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 0721da4..f33cc0e 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -5,6 +5,8 @@ use warnings;
 use Sub::Exporter;
 use Path::Dispatcher;
 
+our $CALLER; # Sub::Exporter doesn't make this available
+
 my $exporter = Sub::Exporter::build_exporter({
     into_level => 1,
     groups => {
@@ -23,13 +25,16 @@ sub import {
         push @{ $pkg . '::ISA' }, $self;
     }
 
+    local $CALLER = $pkg;
     $exporter->($self, @args);
 }
 
 sub build_sugar {
     my ($class, $group, $arg) = @_;
 
-    my $dispatcher = Path::Dispatcher->new;
+    my $dispatcher = Path::Dispatcher->new(
+        name => $CALLER,
+    );
 
     # if this is a subclass, then we want to set up a super dispatcher
     if ($class ne __PACKAGE__) {
diff --git a/t/101-subclass.t b/t/101-subclass.t
index 547d53f..0b7cc5d 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 3;
+use Test::More tests => 5;
 use lib 't/lib';
 use Path::Dispatcher::Test::App;
 
@@ -37,3 +37,7 @@ is_deeply([splice @calls], [
     'app after foo',
 ]);
 
+for ('Path::Dispatcher::Test::Framework', 'Path::Dispatcher::Test::App') {
+    is($_->dispatcher->name, $_, "correct dispatcher name for $_");
+}
+

commit 2cf2b99b8174452b3635ed959e17434a1554ce8f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Fri Aug 1 18:19:39 2008 +0000

    Support for adding stages (plugins!) and adding rules to before/on/after any stage. Rename the before and after stages to first and last.

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index faf4f47..6cc1070 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -6,7 +6,6 @@ use MooseX::AttributeHelpers;
 use Path::Dispatcher::Rule;
 
 sub rule_class { 'Path::Dispatcher::Rule' }
-sub stages { qw/before on after/ }
 
 has _rules => (
     metaclass => 'Collection::Array',
@@ -36,6 +35,23 @@ has name => (
     },
 );
 
+has _stages => (
+    metaclass  => 'Collection::Array',
+    is         => 'rw',
+    isa        => 'ArrayRef[Str]',
+    default    => sub { [ 'on' ] },
+    provides   => {
+        push     => 'push_stage',
+        unshift  => 'unshift_stage',
+    },
+);
+
+sub stages {
+    my $self = shift;
+
+    return ('first', @{ $self->_stages }, 'last');
+}
+
 sub add_rule {
     my $self = shift;
 
@@ -64,28 +80,37 @@ sub dispatch {
         for $self->rules;
 
     for my $stage ($self->stages) {
-        $self->begin_stage($stage, \@matches);
+        for my $substage ('before', 'on', 'after') {
+            my $qualified_stage = $substage eq 'on'
+                                ? $stage
+                                : "${substage}_$stage";
 
-        for my $rule (@{ $rules_for_stage{$stage} || [] }) {
-            my $vars = $rule->match($path)
-                or next;
+            $self->begin_stage($qualified_stage, \@matches);
 
-            push @matches, {
-                stage  => $stage,
-                rule   => $rule,
-                result => $vars,
-            };
+            for my $rule (@{ delete $rules_for_stage{$qualified_stage}||[] }) {
+                my $vars = $rule->match($path)
+                    or next;
 
-            last if !$rule->fallthrough;
-        }
+                push @matches, {
+                    stage  => $qualified_stage,
+                    rule   => $rule,
+                    result => $vars,
+                };
 
-        if ($self->defer_to_super_dispatcher($stage, \@matches)) {
-            push @matches, $self->super_dispatcher->dispatch($path);
-        }
+                last if !$rule->fallthrough;
+            }
 
-        $self->end_stage($stage, \@matches);
+            if ($self->defer_to_super_dispatcher($qualified_stage, \@matches)) {
+                push @matches, $self->super_dispatcher->dispatch($path);
+            }
+
+            $self->end_stage($qualified_stage, \@matches);
+        }
     }
 
+    warn "Unhandled stages: " . join(', ', keys %rules_for_stage)
+        if keys %rules_for_stage;
+
     return if !@matches;
 
     return $self->build_runner(
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index f33cc0e..043152f 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -59,14 +59,14 @@ sub build_sugar {
         },
         before => sub {
             $dispatcher->add_rule(
-                stage => 'before',
+                stage => 'first',
                 regex => $_[0],
                 block => $_[1],
             );
         },
         after => sub {
             $dispatcher->add_rule(
-                stage => 'after',
+                stage => 'last',
                 regex => $_[0],
                 block => $_[1],
             );
diff --git a/t/004-stages.t b/t/004-stages.t
index 5138fc0..36b8418 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -7,14 +7,24 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/after on before/) {
-    $dispatcher->add_rule(
-        stage => $stage,
-        regex => qr/foo/,
-        block => sub { push @calls, $stage },
-    );
+for my $stage (qw/first on last/) {
+    for my $substage (qw/before on after/) {
+        my $qualified_stage = $substage eq 'on'
+                            ? $stage
+                            : "${substage}_$stage";
+
+        $dispatcher->add_rule(
+            stage => $qualified_stage,
+            regex => qr/foo/,
+            block => sub { push @calls, $qualified_stage },
+        );
+    }
 }
 
 $dispatcher->run('foo');
-is_deeply(\@calls, ['before', 'on', 'after']);
+is_deeply(\@calls, [
+    'before_first', 'first', 'after_first',
+    'before_on',    'on',    'after_on',
+    'before_last',  'last',  'after_last',
+]);
 
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index 6fdc102..1dcb11c 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/before on after/) {
+for my $stage (qw/first on last/) {
     for my $number (qw/first second/) {
         $dispatcher->add_rule(
             stage => $stage,
@@ -19,10 +19,10 @@ for my $stage (qw/before on after/) {
 
 $dispatcher->run('foo');
 is_deeply(\@calls, [
-    'before: first',
-    'before: second',
+    'first: first',
+    'first: second',
     'on: first',
-    'after: first',
-    'after: second',
+    'last: first',
+    'last: second',
 ]);
 
diff --git a/t/006-abort.t b/t/006-abort.t
index 68bedae..c174207 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -17,10 +17,10 @@ $dispatcher->add_rule(
 );
 
 $dispatcher->add_rule(
-    stage => 'after',
+    stage => 'last',
     regex => qr/foo/,
     block => sub {
-        push @calls, "after";
+        push @calls, "last";
     },
 );
 
@@ -40,7 +40,7 @@ $dispatcher->add_rule(
     block => sub {
         push @calls, "bar: before";
         my $x = {}->();
-        push @calls, "bar: after";
+        push @calls, "bar: last";
     },
 );
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index 174e400..91057ae 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -17,7 +17,7 @@ ok(!$super_dispatcher->has_super_dispatcher, "no super dispatcher by default");
 ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
 is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");
 
-for my $stage (qw/before on after/) {
+for my $stage (qw/before_on on after_on/) {
     $super_dispatcher->add_rule(
         stage => $stage,
         regex => qr/foo/,
@@ -25,7 +25,7 @@ for my $stage (qw/before on after/) {
     );
 }
 
-for my $stage (qw/before after/) {
+for my $stage (qw/before_on after_on/) {
     $sub_dispatcher->add_rule(
         stage => $stage,
         regex => qr/foo/,
@@ -35,18 +35,18 @@ for my $stage (qw/before after/) {
 
 $super_dispatcher->run('foo');
 is_deeply([splice @calls], [
-    'super before',
+    'super before_on',
     'super on',
-    'super after',
+    'super after_on',
 ]);
 
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
-    'sub before',
-    'super before',
+    'sub before_on',
+    'super before_on',
     'super on',
-    'super after',
-    'sub after',
+    'super after_on',
+    'sub after_on',
 ]);
 
 $sub_dispatcher->add_rule(
@@ -57,8 +57,8 @@ $sub_dispatcher->add_rule(
 
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
-    'sub before',
+    'sub before_on',
     'sub on',
-    'sub after',
+    'sub after_on',
 ]);
 

commit 2087282f828909e2baa1fab2767db40d98548112
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 5 22:42:48 2008 +0000

    Failing tests for dispatching with arguments

diff --git a/t/009-args.t b/t/009-args.t
new file mode 100644
index 0000000..20b85bd
--- /dev/null
+++ b/t/009-args.t
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    regex => qr/foo/,
+    block => sub { push @calls, [@_] },
+);
+
+$dispatcher->run('foo', 42);
+
+is_deeply([splice @calls], [
+    [42],
+]);
+
+my $code = $dispatcher->dispatch('foo');
+$code->(24);
+
+is_deeply([splice @calls], [
+    [24],
+]);
+

commit 82df29dd959d185a76ce7d7dfb4e6fe6db5068c0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 5 22:42:51 2008 +0000

    We can now pass arguments to each rule's block from "run" or invoking the coderef returned by "dispatch"

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 6cc1070..8d73793 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -127,23 +127,25 @@ sub build_runner {
     my $matches = $args{matches};
 
     return sub {
+        my @args = @_;
+
         eval {
             local $SIG{__DIE__} = 'DEFAULT';
             for my $match (@$matches) {
                 if (ref($match) eq 'CODE') {
-                    $match->();
+                    $match->(@args);
                     next;
                 }
 
                 # if we need to set $1, $2..
                 if (ref($match->{result}) eq 'ARRAY') {
                     $self->run_with_number_vars(
-                        sub { $match->{rule}->run($path) },
+                        sub { $match->{rule}->run(@args) },
                         @{ $match->{result} },
                     );
                 }
                 else {
-                    $match->{rule}->run($path);
+                    $match->{rule}->run(@args);
                 }
             }
         };
@@ -169,9 +171,10 @@ sub run_with_number_vars {
 
 sub run {
     my $self = shift;
-    my $code = $self->dispatch(@_);
+    my $path = shift;
+    my $code = $self->dispatch($path);
 
-    return $code->();
+    return $code->(@_);
 }
 
 sub begin_stage {}
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 4a7b70b..e0b97fc 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -82,9 +82,8 @@ sub match {
 
 sub run {
     my $self = shift;
-    my $path = shift;
 
-    $self->block->();
+    $self->block->(@_);
 }
 
 __PACKAGE__->meta->make_immutable;

commit 0e7270d2e513a683529a912d3a13144cd29b3319
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 5 22:52:40 2008 +0000

    Make sure that we return no useful value, so we don't have to worry about backwards compatibility when we figure out how to return values from a dispatch/run

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 8d73793..f6e69ff 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -151,6 +151,8 @@ sub build_runner {
         };
 
         die $@ if $@ && $@ !~ /^Patch::Dispatcher abort\n/;
+
+        return;
     };
 }
 
@@ -174,7 +176,9 @@ sub run {
     my $path = shift;
     my $code = $self->dispatch($path);
 
-    return $code->(@_);
+    $code->(@_);
+
+    return;
 }
 
 sub begin_stage {}
diff --git a/t/010-return.t b/t/010-return.t
new file mode 100644
index 0000000..5e97b53
--- /dev/null
+++ b/t/010-return.t
@@ -0,0 +1,37 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 4;
+use Path::Dispatcher;
+
+# we currently have no defined return strategy :/
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    regex => qr/foo/,
+    block => sub { return @_ },
+);
+
+is_deeply([$dispatcher->run('foo', 42)], []);
+
+my $code = $dispatcher->dispatch('foo');
+is_deeply([$code->(24)], []);
+
+for my $stage (qw/first on last/) {
+    for my $substage (qw/before on after/) {
+        my $qualified_stage = $substage eq 'on'
+                            ? $stage
+                            : "${substage}_$stage";
+        $dispatcher->add_rule(
+            stage => $qualified_stage,
+            regex => qr/foo/,
+            block => sub { return @_ },
+        );
+    }
+}
+
+is_deeply([$dispatcher->run('foo', 42)], []);
+
+$code = $dispatcher->dispatch('foo');
+is_deeply([$code->(24)], []);
+

commit 37d3a19ea1de72239eb1b84a3b2d7ac1f589ed75
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 5 23:15:30 2008 +0000

    Throw an error if "use Path::Dispatcher -base" or similar is used

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index f6e69ff..259abad 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -204,6 +204,14 @@ sub defer_to_super_dispatcher {
     return 1;
 }
 
+sub import {
+    my $self = shift;
+
+    if (@_) {
+        Carp::croak "use Path::Dispatcher (@_) called. Did you mean Path::Dispatcher::Declarative?";
+    }
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit 17177b7b4af7767f812000e19be3fcb3a2d1be82
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 5 23:23:26 2008 +0000

    "use MyApp::Dispatcher" should not export sugar. but "use MyFramework::Dispatcher -base" should

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 043152f..993a4f2 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -24,8 +24,14 @@ sub import {
         no strict 'refs';
         push @{ $pkg . '::ISA' }, $self;
     }
+    else {
+        # we don't want our subclasses exporting our sugar
+        # unless the user specifies -base
+        return if $self ne __PACKAGE__;
+    }
 
     local $CALLER = $pkg;
+
     $exporter->($self, @args);
 }
 

commit fe4152c971a41635973e551929b502b464aa07ee
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 7 17:48:08 2008 +0000

    ignore blib

commit ba7771f136951f2ea5163b2c4c66945a65f14502
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 7 17:48:29 2008 +0000

    Reify dispatch and dispatch match, refactor super dispatcher to just splat its matches into the current dispatch's matches

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 259abad..88c2035 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -4,8 +4,10 @@ use Moose;
 use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Rule;
+use Path::Dispatcher::Dispatch;
 
-sub rule_class { 'Path::Dispatcher::Rule' }
+sub rule_class     { 'Path::Dispatcher::Rule' }
+sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
 has _rules => (
     metaclass => 'Collection::Array',
@@ -76,6 +78,8 @@ sub dispatch {
     my @matches;
     my %rules_for_stage;
 
+    my $dispatch = $self->dispatch_class->new;
+
     push @{ $rules_for_stage{$_->stage} }, $_
         for $self->rules;
 
@@ -91,17 +95,17 @@ sub dispatch {
                 my $vars = $rule->match($path)
                     or next;
 
-                push @matches, {
+                $dispatch->add_match(
                     stage  => $qualified_stage,
                     rule   => $rule,
                     result => $vars,
-                };
-
-                last if !$rule->fallthrough;
+                );
             }
 
             if ($self->defer_to_super_dispatcher($qualified_stage, \@matches)) {
-                push @matches, $self->super_dispatcher->dispatch($path);
+                $dispatch->add_redispatch(
+                    $self->super_dispatcher->dispatch($path)
+                );
             }
 
             $self->end_stage($qualified_stage, \@matches);
@@ -111,72 +115,15 @@ sub dispatch {
     warn "Unhandled stages: " . join(', ', keys %rules_for_stage)
         if keys %rules_for_stage;
 
-    return if !@matches;
-
-    return $self->build_runner(
-        path    => $path,
-        matches => \@matches,
-    );
-}
-
-sub build_runner {
-    my $self = shift;
-    my %args = @_;
-
-    my $path    = $args{path};
-    my $matches = $args{matches};
-
-    return sub {
-        my @args = @_;
-
-        eval {
-            local $SIG{__DIE__} = 'DEFAULT';
-            for my $match (@$matches) {
-                if (ref($match) eq 'CODE') {
-                    $match->(@args);
-                    next;
-                }
-
-                # if we need to set $1, $2..
-                if (ref($match->{result}) eq 'ARRAY') {
-                    $self->run_with_number_vars(
-                        sub { $match->{rule}->run(@args) },
-                        @{ $match->{result} },
-                    );
-                }
-                else {
-                    $match->{rule}->run(@args);
-                }
-            }
-        };
-
-        die $@ if $@ && $@ !~ /^Patch::Dispatcher abort\n/;
-
-        return;
-    };
-}
-
-sub run_with_number_vars {
-    my $self = shift;
-    my $code = shift;
-
-    # we don't have direct write access to $1 and friends, so we have to
-    # do this little hack. the only way we can update $1 is by matching
-    # against a regex (5.10 fixes that)..
-    my $re = join '', map { "(\Q$_\E)" } @_;
-    my $str = join '', @_;
-    $str =~ $re
-        or die "Unable to match '$str' against a copy of itself!";
-
-    $code->();
+    return $dispatch;
 }
 
 sub run {
     my $self = shift;
     my $path = shift;
-    my $code = $self->dispatch($path);
+    my $dispatch = $self->dispatch($path);
 
-    $code->(@_);
+    $dispatch->run(@_);
 
     return;
 }
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
new file mode 100644
index 0000000..02f33d0
--- /dev/null
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -0,0 +1,89 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Dispatch;
+use Moose;
+
+use Path::Dispatcher::Dispatch::Match;
+sub match_class { 'Path::Dispatcher::Dispatch::Match' }
+
+has _matches => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Dispatch::Match]',
+    default   => sub { [] },
+    provides  => {
+        push     => '_add_match',
+        elements => 'matches',
+    },
+);
+
+sub add_redispatch {
+    my $self     = shift;
+    my $dispatch = shift;
+
+    for my $match ($dispatch->matches) {
+        $self->add_match($match);
+    }
+}
+
+sub add_match {
+    my $self = shift;
+
+    my $match;
+
+    # they pass in an already instantiated match..
+    if (@_ == 1 && blessed($_[0])) {
+        $match = shift;
+    }
+    # or they pass in args to create a match..
+    else {
+        $match = $self->match_class->new(@_);
+    }
+
+    $self->_add_match($match);
+}
+
+sub run {
+    my $self = shift;
+    my @args = @_;
+
+    eval {
+        local $SIG{__DIE__} = 'DEFAULT';
+        for my $match ($self->matches) {
+            # if we need to set $1, $2..
+            if ($match->set_number_vars) {
+                $self->run_with_number_vars(
+                    sub { $match->rule->run(@args) },
+                    @{ $match->result },
+                );
+            }
+            else {
+                $match->rule->run(@args);
+            }
+        }
+    };
+
+    die $@ if $@ && $@ !~ /^Patch::Dispatcher abort\n/;
+
+    return;
+}
+
+sub run_with_number_vars {
+    my $self = shift;
+    my $code = shift;
+
+    # we don't have direct write access to $1 and friends, so we have to
+    # do this little hack. the only way we can update $1 is by matching
+    # against a regex (5.10 fixes that)..
+    my $re = join '', map { "(\Q$_\E)" } @_;
+    my $str = join '', @_;
+    $str =~ $re
+        or die "Unable to match '$str' against a copy of itself!";
+
+    $code->();
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
new file mode 100644
index 0000000..79e80fb
--- /dev/null
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Dispatch::Match;
+use Moose;
+
+has stage => (
+    is       => 'ro',
+    isa      => 'Str',
+    required => 1,
+);
+
+has rule => (
+    is       => 'ro',
+    isa      => 'Path::Dispatcher::Rule',
+    required => 1,
+);
+
+has result => (
+    is => 'ro',
+);
+
+has set_number_vars => (
+    is      => 'ro',
+    isa     => 'Bool',
+    lazy    => 1,
+    default => sub { ref(shift->result) eq 'ARRAY' },
+);
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/t/001-api.t b/t/001-api.t
index 7a351ce..10374b6 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 9;
+use Test::More tests => 12;
 use Path::Dispatcher;
 
 my @calls;
@@ -14,10 +14,11 @@ $dispatcher->add_rule(
 
 is_deeply([splice @calls], [], "no calls to the rule block yet");
 
-my $thunk = $dispatcher->dispatch('foo');
+my $dispatch = $dispatcher->dispatch('foo');
 is_deeply([splice @calls], [], "no calls to the rule block yet");
 
-$thunk->();
+isa_ok($dispatch, 'Path::Dispatcher::Dispatch');
+$dispatch->run;
 is_deeply([splice @calls], [ [] ], "finally invoked the rule block");
 
 $dispatcher->run('foo');
@@ -30,10 +31,11 @@ $dispatcher->add_rule(
 
 is_deeply([splice @calls], [], "no calls to the rule block yet");
 
-$thunk = $dispatcher->dispatch('bar');
+$dispatch = $dispatcher->dispatch('bar');
 is_deeply([splice @calls], [], "no calls to the rule block yet");
 
-$thunk->();
+isa_ok($dispatch, 'Path::Dispatcher::Dispatch');
+$dispatch->run;
 is_deeply([splice @calls], [ ['bar', undef] ], "finally invoked the rule block");
 
 $dispatcher->run('bar');
@@ -41,6 +43,7 @@ is_deeply([splice @calls], [ ['bar', undef] ], "invoked the rule block on 'run'"
 
 "foo" =~ /foo/;
 
-$thunk->();
+isa_ok($dispatch, 'Path::Dispatcher::Dispatch');
+$dispatch->run;
 is_deeply([splice @calls], [ ['bar', undef] ], "invoked the rule block on 'run', makes sure \$1 etc are still correctly set");
 
diff --git a/t/003-404.t b/t/003-404.t
index 23674ad..ab45c9c 100644
--- a/t/003-404.t
+++ b/t/003-404.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 2;
+use Test::More tests => 4;
 use Path::Dispatcher;
 
 my @calls;
@@ -12,8 +12,12 @@ $dispatcher->add_rule(
     block => sub { push @calls, [@_] },
 );
 
-my $thunk = $dispatcher->dispatch('bar');
+my $dispatch = $dispatcher->dispatch('bar');
 is_deeply([splice @calls], [], "no calls to the rule block yet");
 
-is($thunk, undef, "no match, no coderef");
+isa_ok($dispatch, 'Path::Dispatcher::Dispatch');
+is($dispatch->matches, 0, "no matches");
+
+$dispatch->run;
+is_deeply([splice @calls], [], "no calls to the rule block");
 
diff --git a/t/006-abort.t b/t/006-abort.t
index c174207..45adf65 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -24,14 +24,14 @@ $dispatcher->add_rule(
     },
 );
 
-my $thunk;
+my $dispatch;
 lives_ok {
-    $thunk = $dispatcher->dispatch('foo');
+    $dispatch = $dispatcher->dispatch('foo');
 };
 is_deeply([splice @calls], [], "no blocks called yet of course");
 
 lives_ok {
-    $thunk->();
+    $dispatch->run;
 };
 is_deeply([splice @calls], ['on'], "correctly aborted the entire dispatch");
 
diff --git a/t/009-args.t b/t/009-args.t
index 20b85bd..3b055e8 100644
--- a/t/009-args.t
+++ b/t/009-args.t
@@ -18,8 +18,8 @@ is_deeply([splice @calls], [
     [42],
 ]);
 
-my $code = $dispatcher->dispatch('foo');
-$code->(24);
+my $dispatch = $dispatcher->dispatch('foo');
+$dispatch->run(24);
 
 is_deeply([splice @calls], [
     [24],
diff --git a/t/010-return.t b/t/010-return.t
index 5e97b53..0bf2f90 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -14,8 +14,8 @@ $dispatcher->add_rule(
 
 is_deeply([$dispatcher->run('foo', 42)], []);
 
-my $code = $dispatcher->dispatch('foo');
-is_deeply([$code->(24)], []);
+my $dispatch = $dispatcher->dispatch('foo');
+is_deeply([$dispatch->run(24)], []);
 
 for my $stage (qw/first on last/) {
     for my $substage (qw/before on after/) {
@@ -32,6 +32,6 @@ for my $stage (qw/first on last/) {
 
 is_deeply([$dispatcher->run('foo', 42)], []);
 
-$code = $dispatcher->dispatch('foo');
-is_deeply([$code->(24)], []);
+$dispatch = $dispatcher->dispatch('foo');
+is_deeply([$dispatch->run(24)], []);
 

commit e8b6416f7925e6ebfff187f843bf4a007e394914
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 7 17:50:43 2008 +0000

    Move the "invoke match" logic into match where it belongs (and now can be subclassed to do interesting things)

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 02f33d0..84ad163 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -49,16 +49,7 @@ sub run {
     eval {
         local $SIG{__DIE__} = 'DEFAULT';
         for my $match ($self->matches) {
-            # if we need to set $1, $2..
-            if ($match->set_number_vars) {
-                $self->run_with_number_vars(
-                    sub { $match->rule->run(@args) },
-                    @{ $match->result },
-                );
-            }
-            else {
-                $match->rule->run(@args);
-            }
+            $match->run(@args);
         }
     };
 
@@ -67,21 +58,6 @@ sub run {
     return;
 }
 
-sub run_with_number_vars {
-    my $self = shift;
-    my $code = shift;
-
-    # we don't have direct write access to $1 and friends, so we have to
-    # do this little hack. the only way we can update $1 is by matching
-    # against a regex (5.10 fixes that)..
-    my $re = join '', map { "(\Q$_\E)" } @_;
-    my $str = join '', @_;
-    $str =~ $re
-        or die "Unable to match '$str' against a copy of itself!";
-
-    $code->();
-}
-
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index 79e80fb..262e20a 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -25,6 +25,36 @@ has set_number_vars => (
     default => sub { ref(shift->result) eq 'ARRAY' },
 );
 
+sub run {
+    my $self = shift;
+    my @args = @_;
+
+    if ($self->set_number_vars) {
+        $self->run_with_number_vars(
+            sub { $self->rule->run(@args) },
+            @{ $self->result },
+        );
+    }
+    else {
+        $self->rule->run(@args);
+    }
+}
+
+sub run_with_number_vars {
+    my $self = shift;
+    my $code = shift;
+
+    # we don't have direct write access to $1 and friends, so we have to
+    # do this little hack. the only way we can update $1 is by matching
+    # against a regex (5.10 fixes that)..
+    my $re = join '', map { "(\Q$_\E)" } @_;
+    my $str = join '', @_;
+    $str =~ $re
+        or die "Unable to match '$str' against a copy of itself!";
+
+    $code->();
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit 2694cc6eb3cc6154c51e62bf2ec5acb041e0683e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:08:45 2008 +0000

    add_rule should not silently create new rules, because we'll be getting multiple rule subclasses

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 88c2035..55ffade 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -15,7 +15,7 @@ has _rules => (
     isa       => 'ArrayRef[Path::Dispatcher::Rule]',
     default   => sub { [] },
     provides  => {
-        push     => '_add_rule',
+        push     => 'add_rule',
         elements => 'rules',
     },
 );
@@ -54,23 +54,6 @@ sub stages {
     return ('first', @{ $self->_stages }, 'last');
 }
 
-sub add_rule {
-    my $self = shift;
-
-    my $rule;
-
-    # they pass in an already instantiated rule..
-    if (@_ == 1 && blessed($_[0])) {
-        $rule = shift;
-    }
-    # or they pass in args to create a rule
-    else {
-        $rule = $self->rule_class->new(@_);
-    }
-
-    $self->_add_rule($rule);
-}
-
 sub dispatch {
     my $self = shift;
     my $path = shift;

commit 98a1f80b108f1618bc59ebd4543226f38e439db5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:08:56 2008 +0000

    We also don't need rule_class any more

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 55ffade..5dfc22a 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -6,7 +6,6 @@ use MooseX::AttributeHelpers;
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;
 
-sub rule_class     { 'Path::Dispatcher::Rule' }
 sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
 has _rules => (

commit 472a3e44fa71d4ecdb50142351c7476cd1c525f9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:09:01 2008 +0000

    Add some Rule subclasses: CodeRef, Regex, Tokens. Regex will probably be subsumed by Tokens

diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
new file mode 100644
index 0000000..b1abbc7
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::CodeRef;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+has matcher => (
+    is       => 'ro',
+    isa      => 'CodeRef',
+    required => 1,
+);
+
+sub _match {
+    my $self = shift;
+    local $_ = shift; # path
+
+    return $self->matcher->($_);
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
new file mode 100644
index 0000000..d1bc395
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Regex;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+has regex => (
+    is       => 'ro',
+    isa      => 'RegexpRef',
+    required => 1,
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    return unless $path =~ $self->regex;
+    return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ];
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
new file mode 100644
index 0000000..c60cea6
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Tokens;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+has tokens => (
+    is       => 'ro',
+    isa      => 'ArrayRef[ArrayRef[Str|RegexpRef]]',
+    required => 1,
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+

commit d805765c7a71e6864c8d17314a733abe783230f3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:09:14 2008 +0000

    Remove the logic in Rule that is controlled by subclasses

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index e0b97fc..4d97af3 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -9,12 +9,6 @@ has stage => (
     required => 1,
 );
 
-has matcher => (
-    is       => 'ro',
-    isa      => 'CodeRef',
-    required => 1,
-);
-
 has block => (
     is       => 'ro',
     isa      => 'CodeRef',
@@ -31,39 +25,11 @@ has fallthrough => (
     },
 );
 
-around BUILDARGS => sub {
-    my $orig = shift;
-    my $self = shift;
-    my $args = $self->$orig(@_);
-
-    if (!$args->{matcher} && $args->{regex}) {
-        $args->{matcher} = $self->build_regex_matcher(delete $args->{regex});
-    }
-
-    return $args;
-};
-
-sub build_regex_matcher {
-    my $self = shift;
-    my $re   = shift;
-
-    # compile the regex immediately, instead of each match
-    $re = qr/$re/;
-
-    return sub {
-        return unless $_ =~ $re;
-
-        my $path = $_;
-        return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ];
-    }
-}
-
 sub match {
     my $self = shift;
     my $path = shift;
 
-    local $_ = $path;
-    my $result = $self->matcher->();
+    my $result = $self->_match($path);
     return unless $result;
 
     # make sure that the returned values are PLAIN STRINGS
@@ -89,5 +55,10 @@ sub run {
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
+# don't require others to load our subclasses explicitly
+require Path::Dispatcher::Rule::CodeRef;
+require Path::Dispatcher::Rule::Regex;
+require Path::Dispatcher::Rule::Tokens;
+
 1;
 

commit 149381ae643d790306bfa15c26c342bb1a94e103
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:09:33 2008 +0000

    Test fixes for the API change in add_rule

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 993a4f2..eefdea2 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -59,22 +59,28 @@ sub build_sugar {
         },
         on => sub {
             $dispatcher->add_rule(
-                regex => $_[0],
-                block => $_[1],
+                Path::Dispatcher::Rule::Regex->new(
+                    regex => $_[0],
+                    block => $_[1],
+                ),
             );
         },
         before => sub {
             $dispatcher->add_rule(
-                stage => 'first',
-                regex => $_[0],
-                block => $_[1],
+                Path::Dispatcher::Rule::Regex->new(
+                    stage => 'first',
+                    regex => $_[0],
+                    block => $_[1],
+                ),
             );
         },
         after => sub {
             $dispatcher->add_rule(
-                stage => 'last',
-                regex => $_[0],
-                block => $_[1],
+                Path::Dispatcher::Rule::Regex->new(
+                    stage => 'last',
+                    regex => $_[0],
+                    block => $_[1],
+                ),
             );
         },
     };
diff --git a/t/001-api.t b/t/001-api.t
index 10374b6..574f960 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -8,8 +8,10 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub { push @calls, [@_] },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub { push @calls, [@_] },
+    ),
 );
 
 is_deeply([splice @calls], [], "no calls to the rule block yet");
@@ -25,8 +27,10 @@ $dispatcher->run('foo');
 is_deeply([splice @calls], [ [] ], "invoked the rule block on 'run'");
 
 $dispatcher->add_rule(
-    regex => qr/(bar)/,
-    block => sub { push @calls, [$1, $2] },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/(bar)/,
+        block => sub { push @calls, [$1, $2] },
+    ),
 );
 
 is_deeply([splice @calls], [], "no calls to the rule block yet");
diff --git a/t/002-rule.t b/t/002-rule.t
index 712d680..48be78a 100644
--- a/t/002-rule.t
+++ b/t/002-rule.t
@@ -6,7 +6,7 @@ use Path::Dispatcher::Rule;
 
 my @calls;
 
-my $rule = Path::Dispatcher::Rule->new(
+my $rule = Path::Dispatcher::Rule::Regex->new(
     regex => qr/^(..)(..)/,
     block => sub {
         push @calls, {
diff --git a/t/003-404.t b/t/003-404.t
index ab45c9c..9d08740 100644
--- a/t/003-404.t
+++ b/t/003-404.t
@@ -8,8 +8,10 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub { push @calls, [@_] },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub { push @calls, [@_] },
+    ),
 );
 
 my $dispatch = $dispatcher->dispatch('bar');
diff --git a/t/004-stages.t b/t/004-stages.t
index 36b8418..96c02eb 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -14,9 +14,11 @@ for my $stage (qw/first on last/) {
                             : "${substage}_$stage";
 
         $dispatcher->add_rule(
-            stage => $qualified_stage,
-            regex => qr/foo/,
-            block => sub { push @calls, $qualified_stage },
+            Path::Dispatcher::Rule::Regex->new(
+                stage => $qualified_stage,
+                regex => qr/foo/,
+                block => sub { push @calls, $qualified_stage },
+            ),
         );
     }
 }
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index 1dcb11c..caa5a97 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -10,9 +10,11 @@ my $dispatcher = Path::Dispatcher->new;
 for my $stage (qw/first on last/) {
     for my $number (qw/first second/) {
         $dispatcher->add_rule(
-            stage => $stage,
-            regex => qr/foo/,
-            block => sub { push @calls, "$stage: $number" },
+            Path::Dispatcher::Rule::Regex->new(
+                stage => $stage,
+                regex => qr/foo/,
+                block => sub { push @calls, "$stage: $number" },
+            ),
         );
     }
 }
diff --git a/t/006-abort.t b/t/006-abort.t
index 45adf65..53e4ad7 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -9,19 +9,23 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub {
-        push @calls, "on";
-        die "Patch::Dispatcher abort\n";
-    },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub {
+            push @calls, "on";
+            die "Patch::Dispatcher abort\n";
+        },
+    ),
 );
 
 $dispatcher->add_rule(
-    stage => 'last',
-    regex => qr/foo/,
-    block => sub {
-        push @calls, "last";
-    },
+    Path::Dispatcher::Rule::Regex->new(
+        stage => 'last',
+        regex => qr/foo/,
+        block => sub {
+            push @calls, "last";
+        },
+    ),
 );
 
 my $dispatch;
@@ -36,12 +40,14 @@ lives_ok {
 is_deeply([splice @calls], ['on'], "correctly aborted the entire dispatch");
 
 $dispatcher->add_rule(
-    regex => qr/bar/,
-    block => sub {
-        push @calls, "bar: before";
-        my $x = {}->();
-        push @calls, "bar: last";
-    },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/bar/,
+        block => sub {
+            push @calls, "bar: before";
+            my $x = {}->();
+            push @calls, "bar: last";
+        },
+    ),
 );
 
 throws_ok {
diff --git a/t/007-coderef-matcher.t b/t/007-coderef-matcher.t
index 2918c46..ad68f47 100644
--- a/t/007-coderef-matcher.t
+++ b/t/007-coderef-matcher.t
@@ -8,8 +8,10 @@ my (@matches, @calls);
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    matcher => sub { push @matches, $_; length > 5 },
-    block   => sub { push @calls, [@_] },
+    Path::Dispatcher::Rule::CodeRef->new(
+        matcher => sub { push @matches, $_; length > 5 },
+        block   => sub { push @calls, [@_] },
+    ),
 );
 
 $dispatcher->run('foobar');
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index 91057ae..eba7364 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -19,17 +19,21 @@ is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher i
 
 for my $stage (qw/before_on on after_on/) {
     $super_dispatcher->add_rule(
-        stage => $stage,
-        regex => qr/foo/,
-        block => sub { push @calls, "super $stage" },
+        Path::Dispatcher::Rule::Regex->new(
+            stage => $stage,
+            regex => qr/foo/,
+            block => sub { push @calls, "super $stage" },
+        ),
     );
 }
 
 for my $stage (qw/before_on after_on/) {
     $sub_dispatcher->add_rule(
-        stage => $stage,
-        regex => qr/foo/,
-        block => sub { push @calls, "sub $stage" },
+        Path::Dispatcher::Rule::Regex->new(
+            stage => $stage,
+            regex => qr/foo/,
+            block => sub { push @calls, "sub $stage" },
+        ),
     );
 }
 
@@ -50,9 +54,11 @@ is_deeply([splice @calls], [
 ]);
 
 $sub_dispatcher->add_rule(
-    stage => 'on',
-    regex => qr/foo/,
-    block => sub { push @calls, "sub on" },
+    Path::Dispatcher::Rule::Regex->new(
+        stage => 'on',
+        regex => qr/foo/,
+        block => sub { push @calls, "sub on" },
+    ),
 );
 
 $sub_dispatcher->run('foo');
diff --git a/t/009-args.t b/t/009-args.t
index 3b055e8..2a3f794 100644
--- a/t/009-args.t
+++ b/t/009-args.t
@@ -8,8 +8,10 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub { push @calls, [@_] },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub { push @calls, [@_] },
+    ),
 );
 
 $dispatcher->run('foo', 42);
diff --git a/t/010-return.t b/t/010-return.t
index 0bf2f90..6e89716 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -8,8 +8,10 @@ use Path::Dispatcher;
 
 my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub { return @_ },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub { return @_ },
+    ),
 );
 
 is_deeply([$dispatcher->run('foo', 42)], []);
@@ -23,9 +25,11 @@ for my $stage (qw/first on last/) {
                             ? $stage
                             : "${substage}_$stage";
         $dispatcher->add_rule(
-            stage => $qualified_stage,
-            regex => qr/foo/,
-            block => sub { return @_ },
+            Path::Dispatcher::Rule::Regex->new(
+                stage => $qualified_stage,
+                regex => qr/foo/,
+                block => sub { return @_ },
+            ),
         );
     }
 }
diff --git a/t/101-subclass.t b/t/101-subclass.t
index 0b7cc5d..67d77c8 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -24,10 +24,12 @@ is_deeply([splice @calls], [
 ]);
 
 Path::Dispatcher::Test::App->dispatcher->add_rule(
-    regex => qr/foo/,
-    block => sub {
-        push @calls, 'app on foo';
-    },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub {
+            push @calls, 'app on foo';
+        },
+    ),
 );
 
 Path::Dispatcher::Test::App->run('foo');

commit 4f3c0f413a7118970c5d957136dfc04225d5dced
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 13 09:36:55 2008 +0000

    Simplest implementation of tokens rule

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index c60cea6..50da12e 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -4,14 +4,32 @@ use Moose;
 extends 'Path::Dispatcher::Rule';
 
 has tokens => (
-    is       => 'ro',
-    isa      => 'ArrayRef[ArrayRef[Str|RegexpRef]]',
-    required => 1,
+    is         => 'ro',
+    isa        => 'ArrayRef[Str]',
+    auto_deref => 1,
+    required   => 1,
+);
+
+has splitter => (
+    is      => 'ro',
+    isa     => 'Str',
+    default => ' ',
 );
 
 sub _match {
     my $self = shift;
     my $path = shift;
+
+    my @tokens = split $self->splitter, $path;
+
+    for my $expected ($self->tokens) {
+        my $got = shift @tokens;
+
+        return if $got ne $expected;
+    }
+
+    return if @tokens;
+    return 1;
 }
 
 __PACKAGE__->meta->make_immutable;

commit 98df5e36c18643dc6ed473332f073bb3055f10a4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:11:45 2008 +0000

    Refactor out the qualifying-stage-names code

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 5dfc22a..fb13f15 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -47,12 +47,28 @@ has _stages => (
     },
 );
 
-sub stages {
+sub stage_names {
     my $self = shift;
 
     return ('first', @{ $self->_stages }, 'last');
 }
 
+sub stages {
+    my $self = shift;
+    my @stages;
+
+    for my $stage ($self->stage_names) {
+        for my $substage ('before', 'on', 'after') {
+            my $qualified_stage = $substage eq 'on'
+                                ? $stage
+                                : "${substage}_$stage";
+            push @stages, $qualified_stage;
+        }
+    }
+
+    return @stages;
+}
+
 sub dispatch {
     my $self = shift;
     my $path = shift;
@@ -66,32 +82,26 @@ sub dispatch {
         for $self->rules;
 
     for my $stage ($self->stages) {
-        for my $substage ('before', 'on', 'after') {
-            my $qualified_stage = $substage eq 'on'
-                                ? $stage
-                                : "${substage}_$stage";
-
-            $self->begin_stage($qualified_stage, \@matches);
-
-            for my $rule (@{ delete $rules_for_stage{$qualified_stage}||[] }) {
-                my $vars = $rule->match($path)
-                    or next;
+        $self->begin_stage($stage, \@matches);
 
-                $dispatch->add_match(
-                    stage  => $qualified_stage,
-                    rule   => $rule,
-                    result => $vars,
-                );
-            }
+        for my $rule (@{ delete $rules_for_stage{$stage}||[] }) {
+            my $vars = $rule->match($path)
+                or next;
 
-            if ($self->defer_to_super_dispatcher($qualified_stage, \@matches)) {
-                $dispatch->add_redispatch(
-                    $self->super_dispatcher->dispatch($path)
-                );
-            }
+            $dispatch->add_match(
+                stage  => $stage,
+                rule   => $rule,
+                result => $vars,
+            );
+        }
 
-            $self->end_stage($qualified_stage, \@matches);
+        if ($self->defer_to_super_dispatcher($stage, \@matches)) {
+            $dispatch->add_redispatch(
+                $self->super_dispatcher->dispatch($path)
+            );
         }
+
+        $self->end_stage($stage, \@matches);
     }
 
     warn "Unhandled stages: " . join(', ', keys %rules_for_stage)
diff --git a/t/004-stages.t b/t/004-stages.t
index 96c02eb..0bb08b1 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -7,20 +7,16 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/first on last/) {
-    for my $substage (qw/before on after/) {
-        my $qualified_stage = $substage eq 'on'
-                            ? $stage
-                            : "${substage}_$stage";
-
-        $dispatcher->add_rule(
-            Path::Dispatcher::Rule::Regex->new(
-                stage => $qualified_stage,
-                regex => qr/foo/,
-                block => sub { push @calls, $qualified_stage },
-            ),
-        );
-    }
+for my $stage (qw/before_first first after_first
+                  before_on    on    after_on
+                  before_last  last  after_last/) {
+    $dispatcher->add_rule(
+        Path::Dispatcher::Rule::Regex->new(
+            stage => $stage,
+            regex => qr/foo/,
+            block => sub { push @calls, $stage },
+        ),
+    );
 }
 
 $dispatcher->run('foo');
diff --git a/t/010-return.t b/t/010-return.t
index 6e89716..918ff3d 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -19,19 +19,16 @@ is_deeply([$dispatcher->run('foo', 42)], []);
 my $dispatch = $dispatcher->dispatch('foo');
 is_deeply([$dispatch->run(24)], []);
 
-for my $stage (qw/first on last/) {
-    for my $substage (qw/before on after/) {
-        my $qualified_stage = $substage eq 'on'
-                            ? $stage
-                            : "${substage}_$stage";
-        $dispatcher->add_rule(
-            Path::Dispatcher::Rule::Regex->new(
-                stage => $qualified_stage,
-                regex => qr/foo/,
-                block => sub { return @_ },
-            ),
-        );
-    }
+for my $stage (qw/before_first first after_first
+                  before_on    on    after_on
+                  before_last  last  after_last/) {
+    $dispatcher->add_rule(
+        Path::Dispatcher::Rule::Regex->new(
+            stage => $stage,
+            regex => qr/foo/,
+            block => sub { return @_ },
+        ),
+    );
 }
 
 is_deeply([$dispatcher->run('foo', 42)], []);

commit 0698fe523e84100764870a4761af2249d1bf56b2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:11:58 2008 +0000

    Always redispatch (now more extensibly), subclasses can disable this for efficiency if they wish

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index fb13f15..fac4460 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -95,11 +95,8 @@ sub dispatch {
             );
         }
 
-        if ($self->defer_to_super_dispatcher($stage, \@matches)) {
-            $dispatch->add_redispatch(
-                $self->super_dispatcher->dispatch($path)
-            );
-        }
+        $dispatch->add_redispatch($self->redispatch($path))
+            if $self->can_redispatch;
 
         $self->end_stage($stage, \@matches);
     }
@@ -110,6 +107,19 @@ sub dispatch {
     return $dispatch;
 }
 
+sub can_redispatch {
+    my $self = shift;
+
+    return $self->has_super_dispatcher;
+}
+
+sub redispatch {
+    my $self = shift;
+    my $path = shift;
+
+    return $self->super_dispatcher->dispatch($path)
+}
+
 sub run {
     my $self = shift;
     my $path = shift;
@@ -123,26 +133,6 @@ sub run {
 sub begin_stage {}
 sub end_stage {}
 
-sub defer_to_super_dispatcher {
-    my $self = shift;
-    my $stage = shift;
-    my $matches = shift;
-
-    return 0 if !$self->has_super_dispatcher;
-
-    # we only defer in the "on" stage.. this is sort of yucky, maybe we want
-    # implicit "before/after" every stage
-    return 0 unless $stage eq 'on';
-
-    # do not defer if we have any matches for this stage
-    return 0 if grep { $_->{stage} eq $stage }
-                grep { ref($_) eq 'HASH' }
-                @$matches;
-
-    # okay, let dad have at it!
-    return 1;
-}
-
 sub import {
     my $self = shift;
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index eba7364..db88926 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -64,7 +64,10 @@ $sub_dispatcher->add_rule(
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
+    'super before_on',
     'sub on',
+    'super on',
     'sub after_on',
+    'super after_on',
 ]);
 

commit cbe46c6bf807176946770e2a5520985d4badd4b4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:12:04 2008 +0000

    Reify dispatch stage so we can have more logic in it

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index fac4460..18bd8ea 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,9 +3,11 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
+use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;
 
+sub stage_class    { 'Path::Dispatcher::Stage' }
 sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
 has _rules => (
@@ -36,37 +38,31 @@ has name => (
     },
 );
 
-has _stages => (
+has stages => (
     metaclass  => 'Collection::Array',
     is         => 'rw',
-    isa        => 'ArrayRef[Str]',
-    default    => sub { [ 'on' ] },
-    provides   => {
-        push     => 'push_stage',
-        unshift  => 'unshift_stage',
-    },
+    isa        => 'ArrayRef[Path::Dispatcher::Stage]',
+    auto_deref => 1,
+    builder    => 'default_stages',
 );
 
-sub stage_names {
-    my $self = shift;
-
-    return ('first', @{ $self->_stages }, 'last');
-}
-
-sub stages {
+sub default_stages {
     my $self = shift;
+    my $stage_class = $self->stage_class;
     my @stages;
 
-    for my $stage ($self->stage_names) {
-        for my $substage ('before', 'on', 'after') {
-            my $qualified_stage = $substage eq 'on'
-                                ? $stage
-                                : "${substage}_$stage";
-            push @stages, $qualified_stage;
+    for my $stage_name (qw/first on last/) {
+        for my $qualifier (qw/before on after/) {
+            my $is_qualified = $qualifier ne 'on';
+            my $stage = $stage_class->new(
+                name => $stage_name,
+                ($is_qualified ? (qualifier => $qualifier) : ()),
+            );
+            push @stages, $stage;
         }
     }
 
-    return @stages;
+    return \@stages;
 }
 
 sub dispatch {
@@ -84,7 +80,9 @@ sub dispatch {
     for my $stage ($self->stages) {
         $self->begin_stage($stage, \@matches);
 
-        for my $rule (@{ delete $rules_for_stage{$stage}||[] }) {
+        my $stage_name = $stage->qualified_name;
+
+        for my $rule (@{ delete $rules_for_stage{$stage_name} || [] }) {
             my $vars = $rule->match($path)
                 or next;
 
diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index 262e20a..18ed839 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -2,9 +2,12 @@
 package Path::Dispatcher::Dispatch::Match;
 use Moose;
 
+use Path::Dispatcher::Stage;
+use Path::Dispatcher::Rule;
+
 has stage => (
     is       => 'ro',
-    isa      => 'Str',
+    isa      => 'Path::Dispatcher::Stage',
     required => 1,
 );
 
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
new file mode 100644
index 0000000..d5405d8
--- /dev/null
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Stage;
+use Moose;
+
+has name => (
+    is  => 'ro',
+    isa => 'Str',
+);
+
+has qualifier => (
+    is        => 'ro',
+    isa       => 'Str',
+    predicate => 'is_qualified',
+);
+
+sub qualified_name {
+    my $self = shift;
+    my $name = $self->name;
+
+    return $self->qualifier . '_' . $name if $self->is_qualified;
+    return $name;
+}
+
+no Moose;
+__PACKAGE__->meta->make_immutable;
+
+1;
+

commit 10dd7469f1064e0e9d1d17c966ca859497405167
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:12:19 2008 +0000

    Allow rules to have a stage object or name

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 18bd8ea..d98a6b2 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -74,7 +74,7 @@ sub dispatch {
 
     my $dispatch = $self->dispatch_class->new;
 
-    push @{ $rules_for_stage{$_->stage} }, $_
+    push @{ $rules_for_stage{$_->stage_name} }, $_
         for $self->rules;
 
     for my $stage ($self->stages) {
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 4d97af3..a2288fa 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -2,9 +2,11 @@
 package Path::Dispatcher::Rule;
 use Moose;
 
+use Path::Dispatcher::Stage;
+
 has stage => (
     is       => 'ro',
-    isa      => 'Str',
+    isa      => 'Str|Path::Dispatcher::Stage',
     default  => 'on',
     required => 1,
 );
@@ -25,6 +27,12 @@ has fallthrough => (
     },
 );
 
+sub stage_name {
+    my $stage = shift->stage;
+    return $stage if !ref($stage);
+    return $stage->qualified_name;
+}
+
 sub match {
     my $self = shift;
     my $path = shift;

commit 114b4d63b6f87ed714b9dc3b42c7e003e502b38d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:12:28 2008 +0000

    Solidify when a match ends the current stage by adding a method to the stage class

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index d98a6b2..f473695 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -77,11 +77,13 @@ sub dispatch {
     push @{ $rules_for_stage{$_->stage_name} }, $_
         for $self->rules;
 
+    STAGE:
     for my $stage ($self->stages) {
         $self->begin_stage($stage, \@matches);
 
         my $stage_name = $stage->qualified_name;
 
+        RULE:
         for my $rule (@{ delete $rules_for_stage{$stage_name} || [] }) {
             my $vars = $rule->match($path)
                 or next;
@@ -91,11 +93,16 @@ sub dispatch {
                 rule   => $rule,
                 result => $vars,
             );
+
+            if ($stage->match_ends_stage) {
+                next STAGE;
+            }
         }
 
         $dispatch->add_redispatch($self->redispatch($path))
             if $self->can_redispatch;
-
+    }
+    continue {
         $self->end_stage($stage, \@matches);
     }
 
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index d5405d8..bb211ff 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -21,6 +21,12 @@ sub qualified_name {
     return $name;
 }
 
+# If we're a before/after (qualified) rule, then yeah, we want to continue
+# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
+sub match_ends_stage {
+    return !shift->is_qualified;
+}
+
 no Moose;
 __PACKAGE__->meta->make_immutable;
 
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index caa5a97..d4caab1 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -22,9 +22,7 @@ for my $stage (qw/first on last/) {
 $dispatcher->run('foo');
 is_deeply(\@calls, [
     'first: first',
-    'first: second',
     'on: first',
     'last: first',
-    'last: second',
 ]);
 

commit 00ec8c96eb98bd9e33193352bc919dc8c7271020
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:12:38 2008 +0000

    Be more helpful in "use Path::Dispatcher 'foo'"

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index f473695..911c953 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -138,11 +138,14 @@ sub run {
 sub begin_stage {}
 sub end_stage {}
 
+# We don't export anything, so if they request something, then try to error
+# helpfully
 sub import {
-    my $self = shift;
+    my $self    = shift;
+    my $package = caller;
 
     if (@_) {
-        Carp::croak "use Path::Dispatcher (@_) called. Did you mean Path::Dispatcher::Declarative?";
+        Carp::croak "use Path::Dispatcher (@_) called by $package. Did you mean Path::Dispatcher::Declarative?";
     }
 }
 

commit 1a757e9a7f65e3ffb68706b50e1930198306834f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:12:54 2008 +0000

    Clean up some old code

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 911c953..712cfed 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -69,17 +69,18 @@ sub dispatch {
     my $self = shift;
     my $path = shift;
 
-    my @matches;
-    my %rules_for_stage;
-
     my $dispatch = $self->dispatch_class->new;
 
+    my %rules_for_stage;
     push @{ $rules_for_stage{$_->stage_name} }, $_
         for $self->rules;
 
     STAGE:
     for my $stage ($self->stages) {
-        $self->begin_stage($stage, \@matches);
+        $self->begin_stage(
+            stage    => $stage,
+            dispatch => $dispatch,
+        );
 
         my $stage_name = $stage->qualified_name;
 
@@ -103,7 +104,10 @@ sub dispatch {
             if $self->can_redispatch;
     }
     continue {
-        $self->end_stage($stage, \@matches);
+        $self->end_stage(
+            stage    => $stage,
+            dispatch => $dispatch,
+        );
     }
 
     warn "Unhandled stages: " . join(', ', keys %rules_for_stage)

commit f63182859d04aee54ce075116172c16949a04bd3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:13:07 2008 +0000

    Rules now belong to stages. Rules are now also stage-agnostic. Much cleaner code and concepts :)

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 712cfed..3a5f3f1 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,6 @@
 #!/usr/bin/env perl
 package Path::Dispatcher;
 use Moose;
-use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
@@ -10,17 +9,6 @@ use Path::Dispatcher::Dispatch;
 sub stage_class    { 'Path::Dispatcher::Stage' }
 sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
-has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
-);
-
 has super_dispatcher => (
     is        => 'rw',
     isa       => 'Path::Dispatcher',
@@ -39,7 +27,6 @@ has name => (
 );
 
 has stages => (
-    metaclass  => 'Collection::Array',
     is         => 'rw',
     isa        => 'ArrayRef[Path::Dispatcher::Stage]',
     auto_deref => 1,
@@ -65,16 +52,24 @@ sub default_stages {
     return \@stages;
 }
 
+# ugh, we should probably use IxHash..
+sub stage {
+    my $self = shift;
+    my $name = shift;
+
+    for my $stage ($self->stages) {
+        return $stage if $stage->qualified_name eq $name;
+    }
+
+    return;
+}
+
 sub dispatch {
     my $self = shift;
     my $path = shift;
 
     my $dispatch = $self->dispatch_class->new;
 
-    my %rules_for_stage;
-    push @{ $rules_for_stage{$_->stage_name} }, $_
-        for $self->rules;
-
     STAGE:
     for my $stage ($self->stages) {
         $self->begin_stage(
@@ -85,7 +80,7 @@ sub dispatch {
         my $stage_name = $stage->qualified_name;
 
         RULE:
-        for my $rule (@{ delete $rules_for_stage{$stage_name} || [] }) {
+        for my $rule ($stage->rules) {
             my $vars = $rule->match($path)
                 or next;
 
@@ -110,9 +105,6 @@ sub dispatch {
         );
     }
 
-    warn "Unhandled stages: " . join(', ', keys %rules_for_stage)
-        if keys %rules_for_stage;
-
     return $dispatch;
 }
 
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index eefdea2..6803321 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -58,7 +58,7 @@ sub build_sugar {
             $dispatcher->run(@_);
         },
         on => sub {
-            $dispatcher->add_rule(
+            $dispatcher->stage('on')->add_rule(
                 Path::Dispatcher::Rule::Regex->new(
                     regex => $_[0],
                     block => $_[1],
@@ -66,18 +66,16 @@ sub build_sugar {
             );
         },
         before => sub {
-            $dispatcher->add_rule(
+            $dispatcher->stage('first')->add_rule(
                 Path::Dispatcher::Rule::Regex->new(
-                    stage => 'first',
                     regex => $_[0],
                     block => $_[1],
                 ),
             );
         },
         after => sub {
-            $dispatcher->add_rule(
+            $dispatcher->stage('last')->add_rule(
                 Path::Dispatcher::Rule::Regex->new(
-                    stage => 'last',
                     regex => $_[0],
                     block => $_[1],
                 ),
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index a2288fa..6cad188 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -4,35 +4,12 @@ use Moose;
 
 use Path::Dispatcher::Stage;
 
-has stage => (
-    is       => 'ro',
-    isa      => 'Str|Path::Dispatcher::Stage',
-    default  => 'on',
-    required => 1,
-);
-
 has block => (
     is       => 'ro',
     isa      => 'CodeRef',
     required => 1,
 );
 
-has fallthrough => (
-    is      => 'ro',
-    isa     => 'Bool',
-    lazy    => 1,
-    default => sub {
-        my $self = shift;
-        $self->stage eq 'on' ? 0 : 1;
-    },
-);
-
-sub stage_name {
-    my $stage = shift->stage;
-    return $stage if !ref($stage);
-    return $stage->qualified_name;
-}
-
 sub match {
     my $self = shift;
     my $path = shift;
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index bb211ff..35c9540 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -2,6 +2,8 @@
 package Path::Dispatcher::Stage;
 use Moose;
 
+use Path::Dispatcher::Rule;
+
 has name => (
     is  => 'ro',
     isa => 'Str',
@@ -13,6 +15,17 @@ has qualifier => (
     predicate => 'is_qualified',
 );
 
+has _rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    default   => sub { [] },
+    provides  => {
+        push     => 'add_rule',
+        elements => 'rules',
+    },
+);
+
 sub qualified_name {
     my $self = shift;
     my $name = $self->name;
diff --git a/t/001-api.t b/t/001-api.t
index 574f960..1ba79f9 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
@@ -26,7 +26,7 @@ is_deeply([splice @calls], [ [] ], "finally invoked the rule block");
 $dispatcher->run('foo');
 is_deeply([splice @calls], [ [] ], "invoked the rule block on 'run'");
 
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/(bar)/,
         block => sub { push @calls, [$1, $2] },
diff --git a/t/003-404.t b/t/003-404.t
index 9d08740..c31576e 100644
--- a/t/003-404.t
+++ b/t/003-404.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
diff --git a/t/004-stages.t b/t/004-stages.t
index 0bb08b1..50aedaa 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -10,9 +10,8 @@ my $dispatcher = Path::Dispatcher->new;
 for my $stage (qw/before_first first after_first
                   before_on    on    after_on
                   before_last  last  after_last/) {
-    $dispatcher->add_rule(
+    $dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
-            stage => $stage,
             regex => qr/foo/,
             block => sub { push @calls, $stage },
         ),
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index d4caab1..0e69f40 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -9,9 +9,8 @@ my @calls;
 my $dispatcher = Path::Dispatcher->new;
 for my $stage (qw/first on last/) {
     for my $number (qw/first second/) {
-        $dispatcher->add_rule(
+        $dispatcher->stage($stage)->add_rule(
             Path::Dispatcher::Rule::Regex->new(
-                stage => $stage,
                 regex => qr/foo/,
                 block => sub { push @calls, "$stage: $number" },
             ),
diff --git a/t/006-abort.t b/t/006-abort.t
index 53e4ad7..755e6a3 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -8,7 +8,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
@@ -18,9 +18,8 @@ $dispatcher->add_rule(
     ),
 );
 
-$dispatcher->add_rule(
+$dispatcher->stage('last')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
-        stage => 'last',
         regex => qr/foo/,
         block => sub {
             push @calls, "last";
@@ -39,7 +38,7 @@ lives_ok {
 };
 is_deeply([splice @calls], ['on'], "correctly aborted the entire dispatch");
 
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/bar/,
         block => sub {
diff --git a/t/007-coderef-matcher.t b/t/007-coderef-matcher.t
index ad68f47..470d53a 100644
--- a/t/007-coderef-matcher.t
+++ b/t/007-coderef-matcher.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my (@matches, @calls);
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::CodeRef->new(
         matcher => sub { push @matches, $_; length > 5 },
         block   => sub { push @calls, [@_] },
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index db88926..db44465 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -18,9 +18,8 @@ ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
 is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");
 
 for my $stage (qw/before_on on after_on/) {
-    $super_dispatcher->add_rule(
+    $super_dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
-            stage => $stage,
             regex => qr/foo/,
             block => sub { push @calls, "super $stage" },
         ),
@@ -28,9 +27,8 @@ for my $stage (qw/before_on on after_on/) {
 }
 
 for my $stage (qw/before_on after_on/) {
-    $sub_dispatcher->add_rule(
+    $sub_dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
-            stage => $stage,
             regex => qr/foo/,
             block => sub { push @calls, "sub $stage" },
         ),
@@ -53,9 +51,8 @@ is_deeply([splice @calls], [
     'sub after_on',
 ]);
 
-$sub_dispatcher->add_rule(
+$sub_dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
-        stage => 'on',
         regex => qr/foo/,
         block => sub { push @calls, "sub on" },
     ),
diff --git a/t/009-args.t b/t/009-args.t
index 2a3f794..ffd3bab 100644
--- a/t/009-args.t
+++ b/t/009-args.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
diff --git a/t/010-return.t b/t/010-return.t
index 918ff3d..d259fc9 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 # we currently have no defined return strategy :/
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { return @_ },
@@ -22,9 +22,8 @@ is_deeply([$dispatch->run(24)], []);
 for my $stage (qw/before_first first after_first
                   before_on    on    after_on
                   before_last  last  after_last/) {
-    $dispatcher->add_rule(
+    $dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
-            stage => $stage,
             regex => qr/foo/,
             block => sub { return @_ },
         ),
diff --git a/t/101-subclass.t b/t/101-subclass.t
index 67d77c8..3c029fb 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -23,7 +23,7 @@ is_deeply([splice @calls], [
     'app after foo',
 ]);
 
-Path::Dispatcher::Test::App->dispatcher->add_rule(
+Path::Dispatcher::Test::App->dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {

commit d4aa3fd1d054fb1c005b29d6ed7f2874309d7fb1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:13:18 2008 +0000

    Misc cleanup

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 3a5f3f1..a4964e9 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -90,9 +90,7 @@ sub dispatch {
                 result => $vars,
             );
 
-            if ($stage->match_ends_stage) {
-                next STAGE;
-            }
+            next STAGE if $stage->match_ends_stage;
         }
 
         $dispatch->add_redispatch($self->redispatch($path))
@@ -108,11 +106,7 @@ sub dispatch {
     return $dispatch;
 }
 
-sub can_redispatch {
-    my $self = shift;
-
-    return $self->has_super_dispatcher;
-}
+sub can_redispatch { shift->has_super_dispatcher }
 
 sub redispatch {
     my $self = shift;
@@ -132,7 +126,7 @@ sub run {
 }
 
 sub begin_stage {}
-sub end_stage {}
+sub end_stage   {}
 
 # We don't export anything, so if they request something, then try to error
 # helpfully
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 84ad163..66cc533 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -1,8 +1,10 @@
 #!/usr/bin/env perl
 package Path::Dispatcher::Dispatch;
 use Moose;
+use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Dispatch::Match;
+
 sub match_class { 'Path::Dispatcher::Dispatch::Match' }
 
 has _matches => (
@@ -13,6 +15,7 @@ has _matches => (
     provides  => {
         push     => '_add_match',
         elements => 'matches',
+        count    => 'has_matches',
     },
 );
 
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index 35c9540..b2121ac 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -1,6 +1,7 @@
 #!/usr/bin/env perl
 package Path::Dispatcher::Stage;
 use Moose;
+use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Rule;
 

commit 5edeed79a5c998f4523a5728cd0b13163598a8b1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:13:25 2008 +0000

    Assign dispatcher names based on class name, instead of always Path::Dispatcher

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index a4964e9..daaa5ad 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -21,7 +21,8 @@ has name => (
     default => do {
         my $i = 0;
         sub {
-            join '-', __PACKAGE__, ++$i;
+            my $self = shift;
+            join '-', blessed($self), ++$i;
         },
     },
 );
@@ -81,13 +82,13 @@ sub dispatch {
 
         RULE:
         for my $rule ($stage->rules) {
-            my $vars = $rule->match($path)
+            my $result = $rule->match($path)
                 or next;
 
             $dispatch->add_match(
                 stage  => $stage,
                 rule   => $rule,
-                result => $vars,
+                result => $result,
             );
 
             next STAGE if $stage->match_ends_stage;

commit d39639c9fac1554c64795fdaa0a9424ce3c2c519
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:13:43 2008 +0000

    Remove another vestigial line

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index daaa5ad..4f0ca47 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -78,8 +78,6 @@ sub dispatch {
             dispatch => $dispatch,
         );
 
-        my $stage_name = $stage->qualified_name;
-
         RULE:
         for my $rule ($stage->rules) {
             my $result = $rule->match($path)

commit d536180cee785e341d949dddf7bfc8a716db0ea8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:13:59 2008 +0000

    Remove the first and last stages. If apps want them, they can add them

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 4f0ca47..0e01c87 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -39,15 +39,13 @@ sub default_stages {
     my $stage_class = $self->stage_class;
     my @stages;
 
-    for my $stage_name (qw/first on last/) {
-        for my $qualifier (qw/before on after/) {
-            my $is_qualified = $qualifier ne 'on';
-            my $stage = $stage_class->new(
-                name => $stage_name,
-                ($is_qualified ? (qualifier => $qualifier) : ()),
-            );
-            push @stages, $stage;
-        }
+    for my $qualifier (qw/before on after/) {
+        my $is_qualified = $qualifier ne 'on';
+        my $stage = $stage_class->new(
+            name => 'on',
+            ($is_qualified ? (qualifier => $qualifier) : ()),
+        );
+        push @stages, $stage;
     }
 
     return \@stages;
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 6803321..cfb9560 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -66,7 +66,7 @@ sub build_sugar {
             );
         },
         before => sub {
-            $dispatcher->stage('first')->add_rule(
+            $dispatcher->stage('before_on')->add_rule(
                 Path::Dispatcher::Rule::Regex->new(
                     regex => $_[0],
                     block => $_[1],
@@ -74,7 +74,7 @@ sub build_sugar {
             );
         },
         after => sub {
-            $dispatcher->stage('last')->add_rule(
+            $dispatcher->stage('after_on')->add_rule(
                 Path::Dispatcher::Rule::Regex->new(
                     regex => $_[0],
                     block => $_[1],
diff --git a/t/004-stages.t b/t/004-stages.t
index 50aedaa..dc261c0 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -7,9 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/before_first first after_first
-                  before_on    on    after_on
-                  before_last  last  after_last/) {
+for my $stage (qw/before_on on after_on/) {
     $dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
@@ -19,9 +17,5 @@ for my $stage (qw/before_first first after_first
 }
 
 $dispatcher->run('foo');
-is_deeply(\@calls, [
-    'before_first', 'first', 'after_first',
-    'before_on',    'on',    'after_on',
-    'before_last',  'last',  'after_last',
-]);
+is_deeply(\@calls, ['before_on', 'on', 'after_on']);
 
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index 0e69f40..4f645ff 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -7,21 +7,15 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/first on last/) {
-    for my $number (qw/first second/) {
-        $dispatcher->stage($stage)->add_rule(
-            Path::Dispatcher::Rule::Regex->new(
-                regex => qr/foo/,
-                block => sub { push @calls, "$stage: $number" },
-            ),
-        );
-    }
+for my $number (qw/first second/) {
+    $dispatcher->stage('on')->add_rule(
+        Path::Dispatcher::Rule::Regex->new(
+            regex => qr/foo/,
+            block => sub { push @calls, $number },
+        ),
+    );
 }
 
 $dispatcher->run('foo');
-is_deeply(\@calls, [
-    'first: first',
-    'on: first',
-    'last: first',
-]);
+is_deeply(\@calls, ['first']);
 
diff --git a/t/006-abort.t b/t/006-abort.t
index 755e6a3..7afb742 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -18,7 +18,7 @@ $dispatcher->stage('on')->add_rule(
     ),
 );
 
-$dispatcher->stage('last')->add_rule(
+$dispatcher->stage('on')->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
diff --git a/t/010-return.t b/t/010-return.t
index d259fc9..e04be24 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -19,9 +19,7 @@ is_deeply([$dispatcher->run('foo', 42)], []);
 my $dispatch = $dispatcher->dispatch('foo');
 is_deeply([$dispatch->run(24)], []);
 
-for my $stage (qw/before_first first after_first
-                  before_on    on    after_on
-                  before_last  last  after_last/) {
+for my $stage (qw/before_on on after_on/) {
     $dispatcher->stage($stage)->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,

commit 7d36576cc0dc3fcfd27fddf61ec3b6f9f427bafd
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:14:08 2008 +0000

    Finally pass all tests by asking stages if we should allow redispatch

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 0e01c87..751c452 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -91,7 +91,8 @@ sub dispatch {
         }
 
         $dispatch->add_redispatch($self->redispatch($path))
-            if $self->can_redispatch;
+            if $stage->allows_redispatch($dispatch)
+            && $self->can_redispatch;
     }
     continue {
         $self->end_stage(
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index b2121ac..7fcae19 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -41,6 +41,19 @@ sub match_ends_stage {
     return !shift->is_qualified;
 }
 
+sub allows_redispatch {
+    my $self     = shift;
+    my $dispatch = shift;
+
+    return 0 if $self->is_qualified;
+
+    for my $match ($dispatch->matches) {
+        return 0 if $match->stage->match_ends_stage;
+    }
+
+    return 1;
+}
+
 no Moose;
 __PACKAGE__->meta->make_immutable;
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index db44465..c400dae 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -61,10 +61,7 @@ $sub_dispatcher->stage('on')->add_rule(
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
-    'super before_on',
     'sub on',
-    'super on',
     'sub after_on',
-    'super after_on',
 ]);
 

commit bbc63cf1bacdcb261bd6e9d3a5691b98e7af28ca
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:14:24 2008 +0000

    Remove begin_stage/end_stage, they'll be better off as method modifiers in a commit or two

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 751c452..b5684f8 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -71,11 +71,6 @@ sub dispatch {
 
     STAGE:
     for my $stage ($self->stages) {
-        $self->begin_stage(
-            stage    => $stage,
-            dispatch => $dispatch,
-        );
-
         RULE:
         for my $rule ($stage->rules) {
             my $result = $rule->match($path)
@@ -94,12 +89,6 @@ sub dispatch {
             if $stage->allows_redispatch($dispatch)
             && $self->can_redispatch;
     }
-    continue {
-        $self->end_stage(
-            stage    => $stage,
-            dispatch => $dispatch,
-        );
-    }
 
     return $dispatch;
 }
@@ -123,9 +112,6 @@ sub run {
     return;
 }
 
-sub begin_stage {}
-sub end_stage   {}
-
 # We don't export anything, so if they request something, then try to error
 # helpfully
 sub import {

commit cdf6dcae1f803d3edb934458528fc238a611b203
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:14:34 2008 +0000

    Begin refactoring the redispatch logic to instead have stages with match_ends_dispatch. If no stage with match_ends_dispatch has a rule that matched, then we'll redispatch.

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index b5684f8..f9f36e0 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -82,14 +82,13 @@ sub dispatch {
                 result => $result,
             );
 
-            next STAGE if $stage->match_ends_stage;
+            return $dispatch if $stage->match_ends_dispatch;
         }
-
-        $dispatch->add_redispatch($self->redispatch($path))
-            if $stage->allows_redispatch($dispatch)
-            && $self->can_redispatch;
     }
 
+    $dispatch->add_redispatch($self->redispatch($path))
+        if $self->can_redispatch;
+
     return $dispatch;
 }
 
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index 7fcae19..a6327f5 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -37,23 +37,10 @@ sub qualified_name {
 
 # If we're a before/after (qualified) rule, then yeah, we want to continue
 # dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
-sub match_ends_stage {
+sub match_ends_dispatch {
     return !shift->is_qualified;
 }
 
-sub allows_redispatch {
-    my $self     = shift;
-    my $dispatch = shift;
-
-    return 0 if $self->is_qualified;
-
-    for my $match ($dispatch->matches) {
-        return 0 if $match->stage->match_ends_stage;
-    }
-
-    return 1;
-}
-
 no Moose;
 __PACKAGE__->meta->make_immutable;
 

commit ef60f667cab48a87eac1033d7f553f24abc0e6b0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:14:42 2008 +0000

    Break up dispatch into a bunch of specialized methods

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index f9f36e0..bc66c27 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -69,21 +69,13 @@ sub dispatch {
 
     my $dispatch = $self->dispatch_class->new;
 
-    STAGE:
     for my $stage ($self->stages) {
-        RULE:
-        for my $rule ($stage->rules) {
-            my $result = $rule->match($path)
-                or next;
-
-            $dispatch->add_match(
-                stage  => $stage,
-                rule   => $rule,
-                result => $result,
-            );
-
-            return $dispatch if $stage->match_ends_dispatch;
-        }
+        my $stop = $self->dispatch_stage(
+            stage    => $stage,
+            dispatch => $dispatch,
+            path     => $path,
+        );
+        last if $stop;
     }
 
     $dispatch->add_redispatch($self->redispatch($path))
@@ -92,6 +84,38 @@ sub dispatch {
     return $dispatch;
 }
 
+sub dispatch_stage {
+    my $self = shift;
+    my %args = @_;
+
+    my $stage = $args{stage};
+
+    for my $rule ($stage->rules) {
+        my $matched = $self->dispatch_rule(
+            %args,
+            rule => $rule,
+        );
+        return 1 if $matched && $stage->match_ends_dispatch;
+    }
+
+    return 0;
+}
+
+sub dispatch_rule {
+    my $self = shift;
+    my %args = @_;
+
+    my $result = $args{rule}->match($args{path})
+        or return 0;
+
+    $args{dispatch}->add_match(
+        %args,
+        result => $result,
+    );
+
+    return 1;
+}
+
 sub can_redispatch { shift->has_super_dispatcher }
 
 sub redispatch {

commit fb47b640b32a57c86b2a2433a0db4f152029e249
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:14:50 2008 +0000

    Add the concept of a "cleanup" stage, a stage that still runs after a dispatch abruptly ends in a stage

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index bc66c27..4caefd8 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -37,18 +37,12 @@ has stages => (
 sub default_stages {
     my $self = shift;
     my $stage_class = $self->stage_class;
-    my @stages;
 
-    for my $qualifier (qw/before on after/) {
-        my $is_qualified = $qualifier ne 'on';
-        my $stage = $stage_class->new(
-            name => 'on',
-            ($is_qualified ? (qualifier => $qualifier) : ()),
-        );
-        push @stages, $stage;
-    }
+    my $before = $stage_class->new(name => 'on', qualifier => 'before');
+    my $after  = $stage_class->new(name => 'on', qualifier => 'after');
+    my $on     = $stage_class->new(name => 'on', cleanup_stage => $after);
 
-    return \@stages;
+    return [$before, $on, $after];
 }
 
 # ugh, we should probably use IxHash..
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index a6327f5..2052f4d 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -16,6 +16,12 @@ has qualifier => (
     predicate => 'is_qualified',
 );
 
+has cleanup_stage => (
+    is        => 'ro',
+    isa       => 'Path::Dispatcher::Stage',
+    predicate => 'has_cleanup_stage',
+);
+
 has _rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',

commit a50507968f9448422b37c3a874d51c1cbd0e77a1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:15:07 2008 +0000

    Check for, and run, cleanup stages if we abort the dispatch

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 4caefd8..44c2499 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -69,7 +69,18 @@ sub dispatch {
             dispatch => $dispatch,
             path     => $path,
         );
-        last if $stop;
+
+        if ($stop) {
+            if ($stage->has_cleanup_stage) {
+                $self->dispatch_stage(
+                    stage    => $stage->cleanup_stage,
+                    dispatch => $dispatch,
+                    path     => $path,
+                );
+            }
+
+            return $dispatch;
+        }
     }
 
     $dispatch->add_redispatch($self->redispatch($path))

commit f01089254d4323df791d7782bc516fa20d5c7586
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 20:57:47 2008 +0000

    Support multiple dispatches when we redispatch (for plugins or MI)

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 44c2499..ee85221 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -83,7 +83,7 @@ sub dispatch {
         }
     }
 
-    $dispatch->add_redispatch($self->redispatch($path))
+    $dispatch->add_redispatches($self->redispatches($path))
         if $self->can_redispatch;
 
     return $dispatch;
@@ -123,7 +123,7 @@ sub dispatch_rule {
 
 sub can_redispatch { shift->has_super_dispatcher }
 
-sub redispatch {
+sub redispatches {
     my $self = shift;
     my $path = shift;
 
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 66cc533..610b711 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -19,12 +19,14 @@ has _matches => (
     },
 );
 
-sub add_redispatch {
-    my $self     = shift;
-    my $dispatch = shift;
+sub add_redispatches {
+    my $self       = shift;
+    my @dispatches = @_;
 
-    for my $match ($dispatch->matches) {
-        $self->add_match($match);
+    for my $dispatch (@dispatches) {
+        for my $match ($dispatch->matches) {
+            $self->add_match($match);
+        }
     }
 }
 

commit dd06ca7752ac69e79f945cccc8279e8ad0e1184a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:21:13 2008 +0000

    We need to "stop" during run, not dispatch, because if something calls next_rule, it's akin to $self->SUPER::method

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index ee85221..40ddc7a 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -64,23 +64,11 @@ sub dispatch {
     my $dispatch = $self->dispatch_class->new;
 
     for my $stage ($self->stages) {
-        my $stop = $self->dispatch_stage(
+        $self->dispatch_stage(
             stage    => $stage,
             dispatch => $dispatch,
             path     => $path,
         );
-
-        if ($stop) {
-            if ($stage->has_cleanup_stage) {
-                $self->dispatch_stage(
-                    stage    => $stage->cleanup_stage,
-                    dispatch => $dispatch,
-                    path     => $path,
-                );
-            }
-
-            return $dispatch;
-        }
     }
 
     $dispatch->add_redispatches($self->redispatches($path))
@@ -96,14 +84,11 @@ sub dispatch_stage {
     my $stage = $args{stage};
 
     for my $rule ($stage->rules) {
-        my $matched = $self->dispatch_rule(
+        $self->dispatch_rule(
             %args,
             rule => $rule,
         );
-        return 1 if $matched && $stage->match_ends_dispatch;
     }
-
-    return 0;
 }
 
 sub dispatch_rule {
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 610b711..5bd8c02 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -55,6 +55,7 @@ sub run {
         local $SIG{__DIE__} = 'DEFAULT';
         for my $match ($self->matches) {
             $match->run(@args);
+            last if $match->ends_dispatch($self);
         }
     };
 
diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index 18ed839..efc20ac 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -58,6 +58,14 @@ sub run_with_number_vars {
     $code->();
 }
 
+# If we're a before/after (qualified) rule, then yeah, we want to continue
+# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
+sub ends_dispatch {
+    my $self = shift;
+
+    return $self->stage->is_qualified ? 0 : 1;
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index 2052f4d..448ed3e 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -41,12 +41,6 @@ sub qualified_name {
     return $name;
 }
 
-# If we're a before/after (qualified) rule, then yeah, we want to continue
-# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
-sub match_ends_dispatch {
-    return !shift->is_qualified;
-}
-
 no Moose;
 __PACKAGE__->meta->make_immutable;
 

commit 4825e972f38c8a3cd18e296841c4056f4b32bf59
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:34:30 2008 +0000

    Eureka. Matching against the empty regex is totally nondeterministic because of older Perl's optimization of //

diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index efc20ac..aa18aec 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -52,8 +52,14 @@ sub run_with_number_vars {
     # against a regex (5.10 fixes that)..
     my $re = join '', map { "(\Q$_\E)" } @_;
     my $str = join '', @_;
-    $str =~ $re
-        or die "Unable to match '$str' against a copy of itself!";
+
+    # we need to check length because Perl's annoying gotcha of the empty regex
+    # actually being an alias for whatever the previously used regex was 
+    # (useful last decade when qr// hadn't been invented)
+    if (length($str)) {
+        $str =~ $re
+            or die "Unable to match '$str' against a copy of itself!";
+    }
 
     $code->();
 }

commit b9bac919ad47c5a68e6dc694f577c81935170f85
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:34:33 2008 +0000

    Support for continuing on to the next rule as if the current rule hadn't been matched (next_rule)

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 5bd8c02..0e6e721 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -54,8 +54,14 @@ sub run {
     eval {
         local $SIG{__DIE__} = 'DEFAULT';
         for my $match ($self->matches) {
-            $match->run(@args);
-            last if $match->ends_dispatch($self);
+            eval {
+                local $SIG{__DIE__} = 'DEFAULT';
+                $match->run(@args);
+
+                no warnings 'exiting';
+                last if $match->ends_dispatch($self);
+            };
+            die $@ if $@ && $@ !~ /^Patch::Dispatcher next rule\n/;
         }
     };
 
diff --git a/t/011-next-rule.t b/t/011-next-rule.t
new file mode 100644
index 0000000..85da76b
--- /dev/null
+++ b/t/011-next-rule.t
@@ -0,0 +1,58 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 6;
+use Test::Exception;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub {
+            push @calls, "on";
+            die "Patch::Dispatcher next rule\n";
+            push @calls, "on post-die?!";
+        },
+    ),
+);
+
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub {
+            push @calls, "last";
+        },
+    ),
+);
+
+my $dispatch;
+lives_ok {
+    $dispatch = $dispatcher->dispatch('foo');
+};
+is_deeply([splice @calls], [], "no blocks called yet of course");
+
+lives_ok {
+    $dispatch->run;
+};
+is_deeply([splice @calls], ['on', 'last'], "correctly continued to the next rule");
+
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/bar/,
+        block => sub {
+            push @calls, "bar: before";
+            my $x = {}->();
+            push @calls, "bar: last";
+        },
+    ),
+);
+
+throws_ok {
+    $dispatcher->run('bar');
+} qr/Not a CODE reference/;
+
+is_deeply([splice @calls], ['bar: before'], "regular dies pass through");
+

commit e74bf49bffc61dd7d2631c6a40fac28bd76196ad
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:46:51 2008 +0000

    Did I really spell it Patch in four different places?

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 0e6e721..0babb6f 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -61,11 +61,11 @@ sub run {
                 no warnings 'exiting';
                 last if $match->ends_dispatch($self);
             };
-            die $@ if $@ && $@ !~ /^Patch::Dispatcher next rule\n/;
+            die $@ if $@ && $@ !~ /^Path::Dispatcher next rule\n/;
         }
     };
 
-    die $@ if $@ && $@ !~ /^Patch::Dispatcher abort\n/;
+    die $@ if $@ && $@ !~ /^Path::Dispatcher abort\n/;
 
     return;
 }
diff --git a/t/006-abort.t b/t/006-abort.t
index 7afb742..f3b2d67 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -13,7 +13,7 @@ $dispatcher->stage('on')->add_rule(
         regex => qr/foo/,
         block => sub {
             push @calls, "on";
-            die "Patch::Dispatcher abort\n";
+            die "Path::Dispatcher abort\n";
         },
     ),
 );
diff --git a/t/011-next-rule.t b/t/011-next-rule.t
index 85da76b..aee1e7d 100644
--- a/t/011-next-rule.t
+++ b/t/011-next-rule.t
@@ -13,7 +13,7 @@ $dispatcher->stage('on')->add_rule(
         regex => qr/foo/,
         block => sub {
             push @calls, "on";
-            die "Patch::Dispatcher next rule\n";
+            die "Path::Dispatcher next rule\n";
             push @calls, "on post-die?!";
         },
     ),

commit a4b6076546e942cc19173c3c1e86501f2926de99
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:46:59 2008 +0000

    Add sugar for next/last rule

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index cfb9560..bab689b 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -81,6 +81,8 @@ sub build_sugar {
                 ),
             );
         },
+        next_rule => sub { die "Path::Dispatcher next rule\n" },
+        last_rule => sub { die "Path::Dispatcher abort\n" },
     };
 }
 
diff --git a/t/102-abort.t b/t/102-abort.t
new file mode 100644
index 0000000..ea0decb
--- /dev/null
+++ b/t/102-abort.t
@@ -0,0 +1,22 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use lib 't/lib';
+use Path::Dispatcher::Test::App;
+
+our @calls;
+
+Path::Dispatcher::Test::App->run('abort');
+is_deeply([splice @calls], [
+    'app before abort',
+]);
+
+Path::Dispatcher::Test::App->run('next rule');
+is_deeply([splice @calls], [
+    'app before next_rule',
+    'app before next_rule 2',
+    'framework before next_rule',
+    'framework before next_rule 2',
+]);
+
diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
index 73af2b8..859eb8c 100644
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -12,5 +12,23 @@ after qr/foo/ => sub {
     push @main::calls, 'app after foo';
 };
 
+before qr/abort/ => sub {
+    push @main::calls, 'app before abort';
+    last_rule;
+    push @main::calls, 'app after abort';
+};
+
+on qr/next rule/ => sub {
+    push @main::calls, 'app before next_rule';
+    next_rule;
+    push @main::calls, 'app after next_rule';
+};
+
+on qr/next rule/ => sub {
+    push @main::calls, 'app before next_rule 2';
+    next_rule;
+    push @main::calls, 'app after next_rule 2';
+};
+
 1;
 
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
index e7d31b1..9cc7c21 100644
--- a/t/lib/Path/Dispatcher/Test/Framework.pm
+++ b/t/lib/Path/Dispatcher/Test/Framework.pm
@@ -16,5 +16,21 @@ after qr/foo/ => sub {
     push @main::calls, 'framework after foo';
 };
 
+on qr/abort/ => sub {
+    push @main::calls, 'framework on abort';
+};
+
+on qr/next rule/ => sub {
+    push @main::calls, 'framework before next_rule';
+    next_rule;
+    push @main::calls, 'framework after next_rule';
+};
+
+on qr/next rule/ => sub {
+    push @main::calls, 'framework before next_rule 2';
+    next_rule;
+    push @main::calls, 'framework after next_rule 2';
+};
+
 1;
 

commit dd016042007f5cbc736bdbd8a5d93696d622883e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:50:22 2008 +0000

    Make sure we clear number variables if we don't do an empty-string match

diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index aa18aec..3d3bf4c 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -60,6 +60,10 @@ sub run_with_number_vars {
         $str =~ $re
             or die "Unable to match '$str' against a copy of itself!";
     }
+    else {
+        # need to clear $1 and friends
+        "x" =~ /^x$/;
+    }
 
     $code->();
 }

commit e36b76f25cef063bea61d26e9273cea48f70d439
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Aug 25 23:53:16 2008 +0000

    Oh. Dynamic scope.

diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index 3d3bf4c..e10d794 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -56,14 +56,10 @@ sub run_with_number_vars {
     # we need to check length because Perl's annoying gotcha of the empty regex
     # actually being an alias for whatever the previously used regex was 
     # (useful last decade when qr// hadn't been invented)
-    if (length($str)) {
-        $str =~ $re
-            or die "Unable to match '$str' against a copy of itself!";
-    }
-    else {
-        # need to clear $1 and friends
-        "x" =~ /^x$/;
-    }
+    # we need to do the match anyway, because we have to clear the number vars
+    ($str, $re) = ("x", "x") if length($str) == 0;
+    $str =~ $re
+        or die "Unable to match '$str' against a copy of itself!";
 
     $code->();
 }

commit 76dc01004453f5545018cb40ed993cf7df35f6bb
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 01:19:42 2008 +0000

    Invoke the "outermost" dispatcher when using declarative's dispatch/run

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index bab689b..ce0122f 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -6,6 +6,7 @@ use Sub::Exporter;
 use Path::Dispatcher;
 
 our $CALLER; # Sub::Exporter doesn't make this available
+our $OUTERMOST_DISPATCHER;
 
 my $exporter = Sub::Exporter::build_exporter({
     into_level => 1,
@@ -51,11 +52,18 @@ sub build_sugar {
         dispatcher => sub { $dispatcher },
         dispatch   => sub {
             shift; # don't need $self
-            $dispatcher->dispatch(@_);
+            local $OUTERMOST_DISPATCHER = $dispatcher
+                if !$OUTERMOST_DISPATCHER;
+
+            $OUTERMOST_DISPATCHER->dispatch(@_);
         },
         run => sub {
             shift; # don't need $self
-            $dispatcher->run(@_);
+
+            local $OUTERMOST_DISPATCHER = $dispatcher
+                if !$OUTERMOST_DISPATCHER;
+
+            $OUTERMOST_DISPATCHER->run(@_);
         },
         on => sub {
             $dispatcher->stage('on')->add_rule(

commit 7bbb64f0473e0b01e94d08c1c47ee37052eee738
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 01:19:55 2008 +0000

    Here shall go the ugly match logic

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 0babb6f..36ae753 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -50,16 +50,18 @@ sub add_match {
 sub run {
     my $self = shift;
     my @args = @_;
+    my @matches = $self->matches;
 
     eval {
         local $SIG{__DIE__} = 'DEFAULT';
-        for my $match ($self->matches) {
+        while (my $match = shift @matches) {
             eval {
-                local $SIG{__DIE__} = 'DEFAULT';
                 $match->run(@args);
 
-                no warnings 'exiting';
-                last if $match->ends_dispatch($self);
+                if ($match->ends_dispatch($self)) {
+                    no warnings 'exiting';
+                    last;
+                }
             };
             die $@ if $@ && $@ !~ /^Path::Dispatcher next rule\n/;
         }

commit 3bf93ed5948aa63e907484e50bafc554c4c297e9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 01:29:07 2008 +0000

    Clean up die handling and expected exceptions (heh).. halve the number of evals we wrap around rule-running code

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 36ae753..c6fdf4c 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -52,22 +52,24 @@ sub run {
     my @args = @_;
     my @matches = $self->matches;
 
-    eval {
-        local $SIG{__DIE__} = 'DEFAULT';
-        while (my $match = shift @matches) {
-            eval {
-                $match->run(@args);
-
-                if ($match->ends_dispatch($self)) {
-                    no warnings 'exiting';
-                    last;
-                }
-            };
-            die $@ if $@ && $@ !~ /^Path::Dispatcher next rule\n/;
-        }
-    };
+    while (my $match = shift @matches) {
+        eval {
+            local $SIG{__DIE__} = 'DEFAULT';
+
+            $match->run(@args);
+
+            if ($match->ends_dispatch($self)) {
+                die "Path::Dispatcher abort\n";
+            }
+        };
 
-    die $@ if $@ && $@ !~ /^Path::Dispatcher abort\n/;
+        if ($@) {
+            return if $@ =~ /^Path::Dispatcher abort\n/;
+            next if $@ =~ /^Path::Dispatcher next rule\n/;
+
+            die $@;
+        }
+    }
 
     return;
 }

commit 88c58dea93b159a60b688217a87f738b3c9e8e76
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 02:34:24 2008 +0000

    Tests for what the rule closures receive as input. Needs improvement!

diff --git a/t/103-input.t b/t/103-input.t
new file mode 100644
index 0000000..741896f
--- /dev/null
+++ b/t/103-input.t
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use lib 't/lib';
+use Path::Dispatcher::Test::App;
+
+our @calls;
+
+Path::Dispatcher::Test::App->run('args', 1..3);
+is_deeply([splice @calls], [
+    {
+        from => 'app',
+        one  => 'g',
+        two  => undef,
+        it   => undef,
+        args => [1, 2, 3],
+    },
+    {
+        from => 'framework',
+        one  => 'g',
+        two  => undef,
+        it   => undef,
+        args => [1, 2, 3],
+    },
+]);
+
diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
index 859eb8c..94a63e4 100644
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -30,5 +30,16 @@ on qr/next rule/ => sub {
     push @main::calls, 'app after next_rule 2';
 };
 
+on qr/ar(g)s/ => sub {
+    push @main::calls, {
+        from => "app",
+        args => [@_],
+        it   => $_,
+        one  => $1,
+        two  => $2,
+    };
+    next_rule;
+};
+
 1;
 
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
index 9cc7c21..c222c14 100644
--- a/t/lib/Path/Dispatcher/Test/Framework.pm
+++ b/t/lib/Path/Dispatcher/Test/Framework.pm
@@ -32,5 +32,15 @@ on qr/next rule/ => sub {
     push @main::calls, 'framework after next_rule 2';
 };
 
+on qr/ar(g)s/ => sub {
+    push @main::calls, {
+        from => "framework",
+        args => [@_],
+        it   => $_,
+        one  => $1,
+        two  => $2,
+    };
+};
+
 1;
 

commit 15c517fc3324f643c4031ffe72026cfa7f7d60d2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 13:26:08 2008 +0000

    Set $_ to the path we matched against

diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index e10d794..a97ee35 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -5,6 +5,11 @@ use Moose;
 use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 
+has path => (
+    is       => 'ro',
+    required => 1,
+);
+
 has stage => (
     is       => 'ro',
     isa      => 'Path::Dispatcher::Stage',
@@ -32,6 +37,8 @@ sub run {
     my $self = shift;
     my @args = @_;
 
+    local $_ = $self->path;
+
     if ($self->set_number_vars) {
         $self->run_with_number_vars(
             sub { $self->rule->run(@args) },
diff --git a/t/103-input.t b/t/103-input.t
index 741896f..fc66548 100644
--- a/t/103-input.t
+++ b/t/103-input.t
@@ -13,14 +13,14 @@ is_deeply([splice @calls], [
         from => 'app',
         one  => 'g',
         two  => undef,
-        it   => undef,
+        it   => 'args',
         args => [1, 2, 3],
     },
     {
         from => 'framework',
         one  => 'g',
         two  => undef,
-        it   => undef,
+        it   => 'args',
         args => [1, 2, 3],
     },
 ]);

commit 73230d62b4861e1bea01a99d883bad8686303ce4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Aug 26 14:55:04 2008 +0000

    We can't discard the invocant if run/dispatch are used as sugar

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index ce0122f..b2ce83d 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -2,9 +2,10 @@
 package Path::Dispatcher::Declarative;
 use strict;
 use warnings;
-use Sub::Exporter;
 use Path::Dispatcher;
 
+use Sub::Exporter;
+
 our $CALLER; # Sub::Exporter doesn't make this available
 our $OUTERMOST_DISPATCHER;
 
@@ -39,8 +40,10 @@ sub import {
 sub build_sugar {
     my ($class, $group, $arg) = @_;
 
+    my $into = $CALLER;
+
     my $dispatcher = Path::Dispatcher->new(
-        name => $CALLER,
+        name => $into,
     );
 
     # if this is a subclass, then we want to set up a super dispatcher
@@ -51,14 +54,19 @@ sub build_sugar {
     return {
         dispatcher => sub { $dispatcher },
         dispatch   => sub {
-            shift; # don't need $self
+            # if caller is $into, then this function is being used as sugar
+            # otherwise, it's probably a method call, so discard the invocant
+            shift if caller ne $into;
+
             local $OUTERMOST_DISPATCHER = $dispatcher
                 if !$OUTERMOST_DISPATCHER;
 
             $OUTERMOST_DISPATCHER->dispatch(@_);
         },
         run => sub {
-            shift; # don't need $self
+            # if caller is $into, then this function is being used as sugar
+            # otherwise, it's probably a method call, so discard the invocant
+            shift if caller ne $into;
 
             local $OUTERMOST_DISPATCHER = $dispatcher
                 if !$OUTERMOST_DISPATCHER;

commit 810ec023ca181b66f7e9e032c8a640535f3ff477
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 14:58:16 2008 +0000

    todoify some tests

diff --git a/t/004-stages.t b/t/004-stages.t
index dc261c0..adb803d 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 3;
 use Path::Dispatcher;
 
 my @calls;
@@ -17,5 +17,11 @@ for my $stage (qw/before_on on after_on/) {
 }
 
 $dispatcher->run('foo');
-is_deeply(\@calls, ['before_on', 'on', 'after_on']);
+is($calls[0], 'before_on');
+is($calls[1], 'on');
+
+TODO: {
+    local $TODO = "after stages not yet working";
+    is($calls[2], 'after_on');
+}
 
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index c400dae..a5564dd 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -39,16 +39,16 @@ $super_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'super before_on',
     'super on',
-    'super after_on',
+#    'super after_on',
 ]);
 
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
+    'sub after_on',
     'super before_on',
     'super on',
-    'super after_on',
-    'sub after_on',
+    #'super after_on',
 ]);
 
 $sub_dispatcher->stage('on')->add_rule(
@@ -62,6 +62,6 @@ $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
     'sub on',
-    'sub after_on',
+    #'sub after_on',
 ]);
 
diff --git a/t/101-subclass.t b/t/101-subclass.t
index 3c029fb..b2434d8 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -11,16 +11,16 @@ Path::Dispatcher::Test::Framework->run('foo');
 is_deeply([splice @calls], [
     'framework before foo',
     'framework on foo',
-    'framework after foo',
+    #'framework after foo',
 ]);
 
 Path::Dispatcher::Test::App->run('foo');
 is_deeply([splice @calls], [
     'app before foo',
+    'app after foo',
     'framework before foo',
     'framework on foo',
-    'framework after foo',
-    'app after foo',
+    #'framework after foo',
 ]);
 
 Path::Dispatcher::Test::App->dispatcher->stage('on')->add_rule(
@@ -36,7 +36,7 @@ Path::Dispatcher::Test::App->run('foo');
 is_deeply([splice @calls], [
     'app before foo',
     'app on foo',
-    'app after foo',
+    #'app after foo',
 ]);
 
 for ('Path::Dispatcher::Test::Framework', 'Path::Dispatcher::Test::App') {

commit 4589243ec4fccbf4a4f800f5861415f78b9a9621
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 14:59:04 2008 +0000

    Remove "cleanup stage" for now

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 40ddc7a..65a1d81 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -39,8 +39,8 @@ sub default_stages {
     my $stage_class = $self->stage_class;
 
     my $before = $stage_class->new(name => 'on', qualifier => 'before');
+    my $on     = $stage_class->new(name => 'on');
     my $after  = $stage_class->new(name => 'on', qualifier => 'after');
-    my $on     = $stage_class->new(name => 'on', cleanup_stage => $after);
 
     return [$before, $on, $after];
 }
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
index 448ed3e..75fda5e 100644
--- a/lib/Path/Dispatcher/Stage.pm
+++ b/lib/Path/Dispatcher/Stage.pm
@@ -16,12 +16,6 @@ has qualifier => (
     predicate => 'is_qualified',
 );
 
-has cleanup_stage => (
-    is        => 'ro',
-    isa       => 'Path::Dispatcher::Stage',
-    predicate => 'has_cleanup_stage',
-);
-
 has _rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',

commit 809ce9b1b06b674a4b5fe068cc9152d9e1c492db
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 14:59:09 2008 +0000

    Minor code cleanup

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 65a1d81..65b7ab4 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -9,12 +9,6 @@ use Path::Dispatcher::Dispatch;
 sub stage_class    { 'Path::Dispatcher::Stage' }
 sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
-has super_dispatcher => (
-    is        => 'rw',
-    isa       => 'Path::Dispatcher',
-    predicate => 'has_super_dispatcher',
-);
-
 has name => (
     is      => 'rw',
     isa     => 'Str',
@@ -34,6 +28,12 @@ has stages => (
     builder    => 'default_stages',
 );
 
+has super_dispatcher => (
+    is        => 'rw',
+    isa       => 'Path::Dispatcher',
+    predicate => 'has_super_dispatcher',
+);
+
 sub default_stages {
     my $self = shift;
     my $stage_class = $self->stage_class;
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index c6fdf4c..17bf30b 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -58,9 +58,8 @@ sub run {
 
             $match->run(@args);
 
-            if ($match->ends_dispatch($self)) {
-                die "Path::Dispatcher abort\n";
-            }
+            die "Path::Dispatcher abort\n"
+                if $match->ends_dispatch($self);
         };
 
         if ($@) {

commit 3e94b45bf595feebca300a9604031514ea1fb8df
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 15:05:37 2008 +0000

    Downgrade MI to 0.70

diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index 8fb6b20..e6758c9 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -30,11 +30,7 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '0.75';
-
-	*inc::Module::Install::VERSION = *VERSION;
-	@inc::Module::Install::ISA     = __PACKAGE__;
-
+	$VERSION = '0.70';
 }
 
 
@@ -85,7 +81,7 @@ END_DIE
 
 # Build.PL was formerly supported, but no longer is due to excessive
 # difficulty in implementing every single feature twice.
-if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+if ( $0 =~ /Build.PL$/i or -f 'Build.PL' ) { die <<"END_DIE" }
 
 Module::Install no longer supports Build.PL.
 
@@ -99,20 +95,14 @@ END_DIE
 
 
 
-# To save some more typing in Module::Install installers, every...
-# use inc::Module::Install
-# ...also acts as an implicit use strict.
-$^H |= strict::bits(qw(refs subs vars));
-
-
-
-
-
 use Cwd        ();
 use File::Find ();
 use File::Path ();
 use FindBin;
 
+*inc::Module::Install::VERSION = *VERSION;
+ at inc::Module::Install::ISA     = __PACKAGE__;
+
 sub autoload {
 	my $self = shift;
 	my $who  = $self->_caller;
@@ -155,7 +145,8 @@ sub import {
 }
 
 sub preload {
-	my $self = shift;
+	my ($self) = @_;
+
 	unless ( $self->{extensions} ) {
 		$self->load_extensions(
 			"$self->{prefix}/$self->{path}", $self
@@ -211,7 +202,6 @@ sub new {
 		$args{path}  =~ s!::!/!g;
 	}
 	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
-	$args{wrote}      = 0;
 
 	bless( \%args, $class );
 }
@@ -287,9 +277,9 @@ sub find_extensions {
 		# correctly.  Otherwise, root through the file to locate the case-preserved
 		# version of the package name.
 		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
-			my $content = Module::Install::_read($subpath . '.pm');
-			my $in_pod  = 0;
-			foreach ( split //, $content ) {
+			open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
+			my $in_pod = 0;
+			while ( <PKGFILE> ) {
 				$in_pod = 1 if /^=\w/;
 				$in_pod = 0 if /^=cut/;
 				next if ($in_pod || /^=cut/);  # skip pod text
@@ -299,6 +289,7 @@ sub find_extensions {
 					last;
 				}
 			}
+			close PKGFILE;
 		}
 
 		push @found, [ $file, $pkg ];
@@ -307,13 +298,6 @@ sub find_extensions {
 	@found;
 }
 
-
-
-
-
-#####################################################################
-# Utility Functions
-
 sub _caller {
 	my $depth = 0;
 	my $call  = caller($depth);
@@ -324,30 +308,6 @@ sub _caller {
 	return $call;
 }
 
-sub _read {
-	local *FH;
-	open FH, "< $_[0]" or die "open($_[0]): $!";
-	my $str = do { local $/; <FH> };
-	close FH or die "close($_[0]): $!";
-	return $str;
-}
-
-sub _write {
-	local *FH;
-	open FH, "> $_[0]" or die "open($_[0]): $!";
-	foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!" }
-	close FH or die "close($_[0]): $!";
-}
-
-sub _version {
-	my $s = shift || 0;
-	   $s =~ s/^(\d+)\.?//;
-	my $l = $1 || 0;
-	my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g;
-	   $l = $l . '.' . join '', @v if @v;
-	return $l + 0;
-}
-
 1;
 
 # Copyright 2008 Adam Kennedy.
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index bd12f2b..5e24ae1 100644
--- a/inc/Module/Install/Base.pm
+++ b/inc/Module/Install/Base.pm
@@ -1,7 +1,7 @@
 #line 1
 package Module::Install::Base;
 
-$VERSION = '0.75';
+$VERSION = '0.70';
 
 # Suspend handler for "redefined" warnings
 BEGIN {
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index 3f436c7..9ce21a4 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -11,7 +11,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 1327f35..2b8f6e8 100644
--- a/inc/Module/Install/Fetch.pm
+++ b/inc/Module/Install/Fetch.pm
@@ -6,7 +6,7 @@ use Module::Install::Base;
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index b7c2ba9..27bbace 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -7,7 +7,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
@@ -63,18 +63,18 @@ sub build_subdirs {
 sub clean_files {
 	my $self  = shift;
 	my $clean = $self->makemaker_args->{clean} ||= {};
-	  %$clean = (
+	%$clean = (
 		%$clean, 
-		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+		FILES => join(' ', grep length, $clean->{FILES}, @_),
 	);
 }
 
 sub realclean_files {
-	my $self      = shift;
+	my $self  = shift;
 	my $realclean = $self->makemaker_args->{realclean} ||= {};
-	  %$realclean = (
+	%$realclean = (
 		%$realclean, 
-		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+		FILES => join(' ', grep length, $realclean->{FILES}, @_),
 	);
 }
 
@@ -121,8 +121,8 @@ sub write {
 	# Generate the 
 	my $args = $self->makemaker_args;
 	$args->{DISTNAME} = $self->name;
-	$args->{NAME}     = $self->module_name || $self->name;
-	$args->{VERSION}  = $self->version;
+	$args->{NAME}     = $self->module_name || $self->name || $self->determine_NAME($args);
+	$args->{VERSION}  = $self->version || $self->determine_VERSION($args);
 	$args->{NAME}     =~ s/-/::/g;
 	if ( $self->tests ) {
 		$args->{test} = { TESTS => $self->tests };
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index ce26bf6..a39ffde 100644
--- a/inc/Module/Install/Metadata.pm
+++ b/inc/Module/Install/Metadata.pm
@@ -6,31 +6,18 @@ use Module::Install::Base;
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
 
 my @scalar_keys = qw{
-	name
-	module_name
-	abstract
-	author
-	version
-	license
-	distribution_type
-	perl_version
-	tests
-	installdirs
+	name module_name abstract author version license
+	distribution_type perl_version tests installdirs
 };
 
 my @tuple_keys = qw{
-	configure_requires
-	build_requires
-	requires
-	recommends
-	bundles
-	resources
+	configure_requires build_requires requires recommends bundles
 };
 
 sub Meta            { shift        }
@@ -46,77 +33,33 @@ foreach my $key (@scalar_keys) {
 	};
 }
 
-sub requires {
-	my $self = shift;
-	while ( @_ ) {
-		my $module  = shift or last;
-		my $version = shift || 0;
-		push @{ $self->{values}->{requires} }, [ $module, $version ];
-	}
-	$self->{values}{requires};
-}
-
-sub build_requires {
-	my $self = shift;
-	while ( @_ ) {
-		my $module  = shift or last;
-		my $version = shift || 0;
-		push @{ $self->{values}->{build_requires} }, [ $module, $version ];
-	}
-	$self->{values}{build_requires};
-}
-
-sub configure_requires {
-	my $self = shift;
-	while ( @_ ) {
-		my $module  = shift or last;
-		my $version = shift || 0;
-		push @{ $self->{values}->{configure_requires} }, [ $module, $version ];
-	}
-	$self->{values}->{configure_requires};
-}
-
-sub recommends {
-	my $self = shift;
-	while ( @_ ) {
-		my $module  = shift or last;
-		my $version = shift || 0;
-		push @{ $self->{values}->{recommends} }, [ $module, $version ];
-	}
-	$self->{values}->{recommends};
-}
-
-sub bundles {
-	my $self = shift;
-	while ( @_ ) {
-		my $module  = shift or last;
-		my $version = shift || 0;
-		push @{ $self->{values}->{bundles} }, [ $module, $version ];
-	}
-	$self->{values}->{bundles};
-}
-
-# Resource handling
-sub resources {
-	my $self = shift;
-	while ( @_ ) {
-		my $resource = shift or last;
-		my $value    = shift or next;
-		push @{ $self->{values}->{resources} }, [ $resource, $value ];
-	}
-	$self->{values}->{resources};
-}
-
-sub repository {
-	my $self = shift;
-	$self->resources( repository => shift );
-	return 1;
+foreach my $key (@tuple_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}{$key} unless @_;
+
+		my @rv;
+		while (@_) {
+			my $module = shift or last;
+			my $version = shift || 0;
+			if ( $module eq 'perl' ) {
+				$version =~ s{^(\d+)\.(\d+)\.(\d+)}
+				             {$1 + $2/1_000 + $3/1_000_000}e;
+				$self->perl_version($version);
+				next;
+			}
+			my $rv = [ $module, $version ];
+			push @rv, $rv;
+		}
+		push @{ $self->{values}{$key} }, @rv;
+		@rv;
+	};
 }
 
 # Aliases for build_requires that will have alternative
 # meanings in some future version of META.yml.
-sub test_requires      { shift->build_requires(@_) }
-sub install_requires   { shift->build_requires(@_) }
+sub test_requires      { shift->build_requires(@_)  }
+sub install_requires   { shift->build_requires(@_)  }
 
 # Aliases for installdirs options
 sub install_as_core    { $_[0]->installdirs('perl')   }
@@ -137,7 +80,7 @@ sub dynamic_config {
 		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
 		return $self;
 	}
-	$self->{values}{dynamic_config} = $_[0] ? 1 : 0;
+	$self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
 	return $self;
 }
 
@@ -152,21 +95,19 @@ sub all_from {
 		die "all_from: cannot find $file from $name" unless -e $file;
 	}
 
-	# Some methods pull from POD instead of code.
-	# If there is a matching .pod, use that instead
-	my $pod = $file;
-	$pod =~ s/\.pm$/.pod/i;
-	$pod = $file unless -e $pod;
-
-	# Pull the different values
-	$self->name_from($file)         unless $self->name;
 	$self->version_from($file)      unless $self->version;
 	$self->perl_version_from($file) unless $self->perl_version;
-	$self->author_from($pod)        unless $self->author;
-	$self->license_from($pod)       unless $self->license;
-	$self->abstract_from($pod)      unless $self->abstract;
 
-	return 1;
+	# The remaining probes read from POD sections; if the file
+	# has an accompanying .pod, use that instead
+	my $pod = $file;
+	if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
+		$file = $pod;
+	}
+
+	$self->author_from($file)   unless $self->author;
+	$self->license_from($file)  unless $self->license;
+	$self->abstract_from($file) unless $self->abstract;
 }
 
 sub provides {
@@ -242,10 +183,10 @@ sub no_index {
 
 sub read {
 	my $self = shift;
-	$self->include_deps( 'YAML::Tiny', 0 );
+	$self->include_deps( 'YAML', 0 );
 
-	require YAML::Tiny;
-	my $data = YAML::Tiny::LoadFile('META.yml');
+	require YAML;
+	my $data = YAML::LoadFile('META.yml');
 
 	# Call methods explicitly in case user has already set some values.
 	while ( my ( $key, $value ) = each %$data ) {
@@ -285,51 +226,35 @@ sub abstract_from {
 	 );
 }
 
-# Add both distribution and module name
-sub name_from {
-	my ($self, $file) = @_;
-	if (
-		Module::Install::_read($file) =~ m/
-		^ \s*
-		package \s*
-		([\w:]+)
-		\s* ;
-		/ixms
-	) {
-		my ($name, $module_name) = ($1, $1);
-		$name =~ s{::}{-}g;
-		$self->name($name);
-		unless ( $self->module_name ) {
-			$self->module_name($module_name);
-		}
-	} else {
-		die "Cannot determine name from $file\n";
-	}
+sub _slurp {
+	local *FH;
+	open FH, "< $_[1]" or die "Cannot open $_[1].pod: $!";
+	do { local $/; <FH> };
 }
 
 sub perl_version_from {
-	my $self = shift;
+	my ( $self, $file ) = @_;
 	if (
-		Module::Install::_read($_[0]) =~ m/
+		$self->_slurp($file) =~ m/
 		^
-		(?:use|require) \s*
+		use \s*
 		v?
 		([\d_\.]+)
 		\s* ;
 		/ixms
 	) {
-		my $perl_version = $1;
-		$perl_version =~ s{_}{}g;
-		$self->perl_version($perl_version);
+		my $v = $1;
+		$v =~ s{_}{}g;
+		$self->perl_version($1);
 	} else {
-		warn "Cannot determine perl version info from $_[0]\n";
+		warn "Cannot determine perl version info from $file\n";
 		return;
 	}
 }
 
 sub author_from {
-	my $self    = shift;
-	my $content = Module::Install::_read($_[0]);
+	my ( $self, $file ) = @_;
+	my $content = $self->_slurp($file);
 	if ($content =~ m/
 		=head \d \s+ (?:authors?)\b \s*
 		([^\n]*)
@@ -343,14 +268,15 @@ sub author_from {
 		$author =~ s{E<gt>}{>}g;
 		$self->author($author);
 	} else {
-		warn "Cannot determine author info from $_[0]\n";
+		warn "Cannot determine author info from $file\n";
 	}
 }
 
 sub license_from {
-	my $self = shift;
+	my ( $self, $file ) = @_;
+
 	if (
-		Module::Install::_read($_[0]) =~ m/
+		$self->_slurp($file) =~ m/
 		(
 			=head \d \s+
 			(?:licen[cs]e|licensing|copyright|legal)\b
@@ -377,7 +303,7 @@ sub license_from {
 			$pattern =~ s{\s+}{\\s+}g;
 			if ( $license_text =~ /\b$pattern\b/i ) {
 				if ( $osi and $license_text =~ /All rights reserved/i ) {
-					print "WARNING: 'All rights reserved' in copyright may invalidate Open Source license.\n";
+					warn "LEGAL WARNING: 'All rights reserved' may invalidate Open Source licenses. Consider removing it.";
 				}
 				$self->license($license);
 				return 1;
@@ -385,23 +311,8 @@ sub license_from {
 		}
 	}
 
-	warn "Cannot determine license info from $_[0]\n";
+	warn "Cannot determine license info from $file\n";
 	return 'unknown';
 }
 
-sub install_script {
-	my $self = shift;
-	my $args = $self->makemaker_args;
-	my $exe  = $args->{EXE_FILES} ||= [];
-        foreach ( @_ ) {
-		if ( -f $_ ) {
-			push @$exe, $_;
-		} elsif ( -d 'script' and -f "script/$_" ) {
-			push @$exe, "script/$_";
-		} else {
-			die "Cannot find script '$_'";
-		}
-	}
-}
-
 1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index c97701b..21a81ab 100644
--- a/inc/Module/Install/Win32.pm
+++ b/inc/Module/Install/Win32.pm
@@ -6,7 +6,7 @@ use Module::Install::Base;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index e80deb8..a05592d 100644
--- a/inc/Module/Install/WriteAll.pm
+++ b/inc/Module/Install/WriteAll.pm
@@ -6,7 +6,7 @@ use Module::Install::Base;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.75';
+	$VERSION = '0.70';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }

commit 028a5fc0c38356fa26d785da1bea9da3caa6b967
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 15:05:44 2008 +0000

    dist doc

diff --git a/Changes b/Changes
index e69de29..be40a43 100644
--- a/Changes
+++ b/Changes
@@ -0,0 +1,5 @@
+Revision history for Path-Dispatcher
+
+0.01    Wed Aug 27 11:04:18 2008
+        Initial release
+
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 65b7ab4..44783b9 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -2,6 +2,8 @@
 package Path::Dispatcher;
 use Moose;
 
+our $VERSION = '0.01';
+
 use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;
@@ -141,3 +143,38 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher - flexible dispatch
+
+=head1 DESCRIPTION
+
+We really like L<Jifty::Dispatcher> and wanted to use it for the command line.
+
+More documentation coming later, there's a lot here..
+
+=head1 AUTHOR
+
+Shawn M Moore, C<< <sartak at bestpractical.com> >>
+
+=head1 BUGS
+
+C<after> substages are not yet run properly when primary stage is run.
+
+The order matches when a super dispatch is added B<will> change.
+
+Please report any bugs or feature requests to
+C<bug-path-dispatcher at rt.cpan.org>, or through the web interface at
+L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Path-Dispatcher>.
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2008 Best Practical Solutions.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
+

commit b4856518d60d4df72f7c0b8c5172b7b2af555e6f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 15:09:55 2008 +0000

    Bump to 0.02

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 44783b9..9a5bb3c 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -2,7 +2,7 @@
 package Path::Dispatcher;
 use Moose;
 
-our $VERSION = '0.01';
+our $VERSION = '0.02';
 
 use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;

commit aa0127be7d20ec90196dec60104cc1e4266f247b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Aug 27 15:57:09 2008 +0000

    Add a synopsis which uses the API

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 9a5bb3c..6dba7db 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -149,6 +149,29 @@ __END__
 
 Path::Dispatcher - flexible dispatch
 
+=head1 SYNOPSIS
+
+    use Path::Dispatcher;
+    my $dispatcher = Path::Dispatcher->new;
+
+    $dispatcher->stage("on")->add_rule(
+        Path::Dispacher::Rule::Regex->new(
+            regex => qr{^/(foo)/.*},
+            block => sub { warn $1; }, # foo
+        )
+    );
+
+    $dispatcher->stage("on")->add_rule(
+        Path::Dispacher::Rule::CodeRef->new(
+            matcher => sub { /^\d+$/ && $_ % 2 == 0 },
+            block   => sub { warn "$_ is an even number" },
+        )
+    );
+
+    my $dispatch = $dispatcher->dispatch("/foo/bar");
+    die "404" unless $dispatch->has_matches;
+    $dispatch->run;
+
 =head1 DESCRIPTION
 
 We really like L<Jifty::Dispatcher> and wanted to use it for the command line.

commit 6fea4591260511ec3820b7ab698ae6f8be55ffb8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 28 02:50:23 2008 +0000

    Add an "intersection" compound rule

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 6cad188..14023a0 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -44,6 +44,7 @@ no Moose;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
+require Path::Dispatcher::Rule::Intersection;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
new file mode 100644
index 0000000..ebde1ed
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -0,0 +1,47 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Intersection;
+use Moose;
+use MooseX::AttributeHelpers;
+
+extends 'Path::Dispatcher::Rule';
+
+has _rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    init_arg  => 'rules',
+    default   => sub { [] },
+    provides  => {
+        push     => 'add_rule',
+        elements => 'rules',
+    },
+);
+
+has '+block' => (
+    required => 0,
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    for my $rule ($self->rules) {
+        return 0 unless $rule->match($path);
+    }
+
+    return 1;
+}
+
+sub run {
+    my $self = shift;
+    my @rules = $self->rules;
+    for my $rule (@rules) {
+        $rule->run(@_);
+    }
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
diff --git a/t/012-intersection.t b/t/012-intersection.t
new file mode 100644
index 0000000..c70b4e3
--- /dev/null
+++ b/t/012-intersection.t
@@ -0,0 +1,59 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 12;
+use Path::Dispatcher;
+
+my @calls;
+
+my $rule = Path::Dispatcher::Rule::Intersection->new;
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->stage('on')->add_rule($rule);
+
+my $dispatch = $dispatcher->dispatch('foobar');
+my @matches = $dispatch->matches;
+is(@matches, 1, "got a match");
+is($matches[0]->rule, $rule, "empty intersection rule matches");
+$dispatch->run;
+is_deeply([splice @calls], [], "no calls yet..");
+
+$rule->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foobar/,
+        block => sub { push @calls, 'foobar' },
+    ),
+);
+
+$dispatch = $dispatcher->dispatch('foobar');
+ at matches = $dispatch->matches;
+is(@matches, 1, "got a match");
+is($matches[0]->rule, $rule, "intersection rule matches");
+$dispatch->run;
+is_deeply([splice @calls], ['foobar'], "foobar block called");
+
+$dispatch = $dispatcher->dispatch('baz');
+ at matches = $dispatch->matches;
+is(@matches, 0, "no matches");
+
+$rule->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/baz/,
+        block => sub { push @calls, 'baz' },
+    ),
+);
+
+$dispatch = $dispatcher->dispatch('foobar');
+ at matches = $dispatch->matches;
+is(@matches, 0, "no matches, because we need to match foobar AND baz");
+
+$dispatch = $dispatcher->dispatch('baz');
+ at matches = $dispatch->matches;
+is(@matches, 0, "no matches, because we need to match foobar AND baz");
+
+$dispatch = $dispatcher->dispatch('foobarbaz');
+ at matches = $dispatch->matches;
+is(@matches, 1, "got a match");
+is($matches[0]->rule, $rule, "intersection rule matches");
+$dispatch->run;
+is_deeply([splice @calls], ['foobar', 'baz'], "both blocks called");
+

commit 91d7351466da7d49f47fa3d285ff834d0b622515
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 28 03:11:39 2008 +0000

    Allow regexes in the token matcher, and write tests

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 50da12e..2e13e70 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -1,11 +1,28 @@
 #!/usr/bin/env perl
 package Path::Dispatcher::Rule::Tokens;
 use Moose;
+use Moose::Util::TypeConstraints;
 extends 'Path::Dispatcher::Rule';
 
+# a token may be
+#   - a string
+#   - a regular expression
+
+# this will be extended to add
+#   - an array reference containing (alternations)
+#     - strings
+#     - regular expressions
+
+my $Str       = find_type_constraint('Str');
+my $RegexpRef = find_type_constraint('RegexpRef');
+
+subtype 'Path::Dispatcher::Token'
+     => as 'Defined'
+     => where { $Str->check($_) || $RegexpRef->check($_) };
+
 has tokens => (
     is         => 'ro',
-    isa        => 'ArrayRef[Str]',
+    isa        => 'ArrayRef[Path::Dispatcher::Token]',
     auto_deref => 1,
     required   => 1,
 );
@@ -20,20 +37,36 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    my @tokens = split $self->splitter, $path;
+    my @orig_tokens = split $self->splitter, $path;
+    my @tokens = @orig_tokens;
 
     for my $expected ($self->tokens) {
         my $got = shift @tokens;
+        return unless $self->_match_token($got, $expected);
+    }
 
-        return if $got ne $expected;
+    return if @tokens; # too many words
+    return [@orig_tokens];
+}
+
+sub _match_token {
+    my $self     = shift;
+    my $got      = shift;
+    my $expected = shift;
+
+    if ($Str->check($expected)) {
+        return $got eq $expected;
+    }
+    elsif ($RegexpRef->check($expected)) {
+        return $got =~ $expected;
     }
 
-    return if @tokens;
-    return 1;
+    return 0;
 }
 
 __PACKAGE__->meta->make_immutable;
 no Moose;
+no Moose::Util::TypeConstraints;
 
 1;
 
diff --git a/t/013-tokens.t b/t/013-tokens.t
new file mode 100644
index 0000000..6fcf521
--- /dev/null
+++ b/t/013-tokens.t
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 4;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => ['foo', 'bar'],
+        block  => sub { push @calls, [$1, $2, $3] },
+    ),
+);
+
+$dispatcher->run('foo bar');
+is_deeply([splice @calls], [ ['foo', 'bar', undef] ], "correctly populated number vars from [str, str] token rule");
+
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => ['foo', qr/bar/],
+        block  => sub { push @calls, [$1, $2, $3] },
+    ),
+);
+
+$dispatcher->run('foo bar');
+is_deeply([splice @calls], [ ['foo', 'bar', undef] ], "ran the first [str, str] rule");
+
+$dispatcher->run('foo barbaz');
+is_deeply([splice @calls], [ ['foo', 'barbaz', undef] ], "ran the second [str, regex] rule");
+
+$dispatcher->run('foo bar baz');
+is_deeply([splice @calls], [], "no matches");
+

commit de5b5016cd7269a2fbb8f734d551411950c094aa
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Aug 28 03:23:37 2008 +0000

    Support for alternation in the tokens rules

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 2e13e70..24bc71e 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -15,14 +15,22 @@ extends 'Path::Dispatcher::Rule';
 
 my $Str       = find_type_constraint('Str');
 my $RegexpRef = find_type_constraint('RegexpRef');
+my $ArrayRef  = find_type_constraint('ArrayRef');
 
 subtype 'Path::Dispatcher::Token'
      => as 'Defined'
      => where { $Str->check($_) || $RegexpRef->check($_) };
 
+subtype 'Path::Dispatcher::TokenAlternation'
+     => as 'ArrayRef[Path::Dispatcher::Token]';
+
+subtype 'Path::Dispatcher::Tokens'
+     => as 'ArrayRef[Path::Dispatcher::Token|Path::Dispatcher::TokenAlternation]';
+
 has tokens => (
     is         => 'ro',
-    isa        => 'ArrayRef[Path::Dispatcher::Token]',
+    isa        => 'Path::Dispatcher::Tokens',
+    isa        => 'ArrayRef',
     auto_deref => 1,
     required   => 1,
 );
@@ -54,7 +62,12 @@ sub _match_token {
     my $got      = shift;
     my $expected = shift;
 
-    if ($Str->check($expected)) {
+    if ($ArrayRef->check($expected)) {
+        for my $alternative (@$expected) {
+            return 1 if $self->_match_token($got, $alternative);
+        }
+    }
+    elsif ($Str->check($expected)) {
         return $got eq $expected;
     }
     elsif ($RegexpRef->check($expected)) {
diff --git a/t/013-tokens.t b/t/013-tokens.t
index 6fcf521..ddeeab2 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 4;
+use Test::More tests => 9;
 use Path::Dispatcher;
 
 my @calls;
@@ -33,3 +33,32 @@ is_deeply([splice @calls], [ ['foo', 'barbaz', undef] ], "ran the second [str, r
 $dispatcher->run('foo bar baz');
 is_deeply([splice @calls], [], "no matches");
 
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => [["Bat", "Super"], "Man"],
+        block  => sub { push @calls, [$1, $2, $3] },
+    ),
+);
+
+$dispatcher->run('Super Man');
+is_deeply([splice @calls], [ ['Super', 'Man', undef] ], "ran the [ [Str,Str], Str ] rule");
+
+$dispatcher->run('Bat Man');
+is_deeply([splice @calls], [ ['Bat', 'Man', undef] ], "ran the [ [Str,Str], Str ] rule");
+
+$dispatcher->run('Aqua Man');
+is_deeply([splice @calls], [ ], "no match");
+
+$dispatcher->stage('on')->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => [[[[qr/Deep/]]], "Man"],
+        block  => sub { push @calls, [$1, $2, $3] },
+    ),
+);
+
+$dispatcher->run('Deep Man');
+is_deeply([splice @calls], [ ['Deep', 'Man', undef] ], "alternations can be arbitrarily deep");
+
+$dispatcher->run('Not Appearing in this Dispatcher Man');
+is_deeply([splice @calls], [ ], "no match");
+

commit 849da73b3e2f8d0bf2ff87ddd32f6bdde1f70adc
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Sep 2 18:30:52 2008 +0000

    Better intuition of rule class in Path::Dispatcher::Declarative

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index b2ce83d..bfc3753 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -74,33 +74,55 @@ sub build_sugar {
             $OUTERMOST_DISPATCHER->run(@_);
         },
         on => sub {
-            $dispatcher->stage('on')->add_rule(
-                Path::Dispatcher::Rule::Regex->new(
-                    regex => $_[0],
-                    block => $_[1],
-                ),
-            );
+            _add_rule($dispatcher, 'on', @_);
         },
         before => sub {
-            $dispatcher->stage('before_on')->add_rule(
-                Path::Dispatcher::Rule::Regex->new(
-                    regex => $_[0],
-                    block => $_[1],
-                ),
-            );
+            _add_rule($dispatcher, 'before_on', @_);
         },
         after => sub {
-            $dispatcher->stage('after_on')->add_rule(
-                Path::Dispatcher::Rule::Regex->new(
-                    regex => $_[0],
-                    block => $_[1],
-                ),
-            );
+            _add_rule($dispatcher, 'after_on', @_);
         },
         next_rule => sub { die "Path::Dispatcher next rule\n" },
         last_rule => sub { die "Path::Dispatcher abort\n" },
     };
 }
 
+my %rule_creator = (
+    ARRAY => sub {
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => $_[0],
+            block  => $_[1],
+        ),
+    },
+    CODE => sub {
+        Path::Dispatcher::Rule::CodeRef->new(
+            matcher => $_[0],
+            block   => $_[1],
+        ),
+    },
+    Regexp => sub {
+        Path::Dispatcher::Rule::Regex->new(
+            regex => $_[0],
+            block => $_[1],
+        ),
+    },
+    '' => sub {
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => [ $_[0] ],
+            block  => $_[1],
+        ),
+    },
+);
+
+sub _add_rule {
+    my ($dispatcher, $stage, $matcher, $block) = @_;
+
+    my $rule_creator = $rule_creator{ ref $matcher }
+        or die "I don't know how to create a rule for type $matcher";
+    my $rule = $rule_creator->($matcher, $block);
+
+    $dispatcher->stage($stage)->add_rule($rule);
+}
+
 1;
 

commit d9d1ff052e71fa20ad8d000ecd6c4fd3d584f4ad
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Sep 25 19:28:05 2008 +0000

    Avoid undef warnings and possible false positives by bailing out if we run out of input

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 24bc71e..721fede 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -49,6 +49,7 @@ sub _match {
     my @tokens = @orig_tokens;
 
     for my $expected ($self->tokens) {
+        return unless @tokens; # too few words
         my $got = shift @tokens;
         return unless $self->_match_token($got, $expected);
     }

commit dbc4cdeb278d5a00fc8809a784b1eaa53213876a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 08:02:39 2008 +0000

    Begin implementation of prefix matching

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 14023a0..5ba47b0 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -10,13 +10,24 @@ has block => (
     required => 1,
 );
 
+has prefix => (
+    is      => 'ro',
+    isa     => 'Bool',
+    default => 0,
+);
+
 sub match {
     my $self = shift;
     my $path = shift;
 
-    my $result = $self->_match($path);
+    my ($result, $leftover) = $self->_match($path);
     return unless $result;
 
+    # if we're not matching only a prefix then require the leftover to be empty
+    return if defined($leftover)
+           && length($leftover)
+           && !$self->prefix;
+
     # make sure that the returned values are PLAIN STRINGS
     # later we will stick them into a regular expression to populate $1 etc
     # which will blow up later!

commit 7a84b34b41d6a0ca142217495e4ae413fcea66f0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 08:25:31 2008 +0000

    Support for prefix matches in the tokens rule, rename the attribute "splitter" to "delimiter"

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 721fede..c67ba5d 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -35,7 +35,7 @@ has tokens => (
     required   => 1,
 );
 
-has splitter => (
+has delimiter => (
     is      => 'ro',
     isa     => 'Str',
     default => ' ',
@@ -45,17 +45,18 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    my @orig_tokens = split $self->splitter, $path;
-    my @tokens = @orig_tokens;
+    my @tokens = split $self->delimiter, $path;
+    my @matched;
 
     for my $expected ($self->tokens) {
         return unless @tokens; # too few words
         my $got = shift @tokens;
         return unless $self->_match_token($got, $expected);
+        push @matched, $got;
     }
 
-    return if @tokens; # too many words
-    return [@orig_tokens];
+    my $leftover = join $self->delimiter, @tokens;
+    return \@matched, $leftover;
 }
 
 sub _match_token {

commit 5fde7ed8346283f3bc758e47851703762463bc95
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 08:25:33 2008 +0000

    Add tests for prefix matches

diff --git a/t/014-tokens-prefix.t b/t/014-tokens-prefix.t
new file mode 100644
index 0000000..3bc020d
--- /dev/null
+++ b/t/014-tokens-prefix.t
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 5;
+use Path::Dispatcher;
+
+my @calls;
+
+my $rule = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['foo', 'bar'],
+    block  => sub { push @calls, [$1, $2, $3] },
+    prefix => 1,
+);
+
+ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
+ok($rule->match('foo bar'), "prefix matches the full path");
+ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
+
+is_deeply($rule->match('foo bar baz'), ["foo", "bar"], "match returns just the results");
+is_deeply([$rule->_match('foo bar baz')], [
+    ["foo", "bar"],
+    "baz"
+], "_match returns the results and the rest of the path");
+

commit 1d1cf54ef8a97a345a9c378955c416343fece5a7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 08:57:07 2008 +0000

    Regex prefix implementation and tests

diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index d1bc395..9a4f53a 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -14,7 +14,16 @@ sub _match {
     my $path = shift;
 
     return unless $path =~ $self->regex;
-    return [ map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#- ];
+
+    my @matches = map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#-;
+
+    # if $' is in the program at all, then it slows down every single regex
+    # we only want to include it if we have to
+    if ($self->prefix) {
+        return \@matches, eval q{$'};
+    }
+
+    return \@matches;
 }
 
 __PACKAGE__->meta->make_immutable;
diff --git a/t/015-regex-prefix.t b/t/015-regex-prefix.t
new file mode 100644
index 0000000..9f6827b
--- /dev/null
+++ b/t/015-regex-prefix.t
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 5;
+use Path::Dispatcher;
+
+my @calls;
+
+my $rule = Path::Dispatcher::Rule::Regex->new(
+    regex  => qr/^(foo)\s*(bar)/,
+    block  => sub { push @calls, [$1, $2] },
+    prefix => 1,
+);
+
+ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
+ok($rule->match('foo bar'), "prefix matches the full path");
+ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
+
+is_deeply($rule->match('foobar baz'), ["foo", "bar"], "match returns just the results");
+is_deeply([$rule->_match('foobar:baz')], [
+    ["foo", "bar"],
+    ":baz"
+], "_match returns the results and the rest of the path");
+
+

commit b36b861f35ea6b9dff44bf72b09baf30211cef87
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:24:17 2008 +0000

    Return undef if there's no match in Intersection

diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
index ebde1ed..ffc348b 100644
--- a/lib/Path/Dispatcher/Rule/Intersection.pm
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -26,7 +26,7 @@ sub _match {
     my $path = shift;
 
     for my $rule ($self->rules) {
-        return 0 unless $rule->match($path);
+        return unless $rule->match($path);
     }
 
     return 1;

commit ba57409375001809e950985c24c20d14946aadb7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:24:36 2008 +0000

    Remove intersections for now, not worth supporting them if they're not being used yet

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 5ba47b0..3a33eb4 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -55,7 +55,6 @@ no Moose;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
-require Path::Dispatcher::Rule::Intersection;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
deleted file mode 100644
index ffc348b..0000000
--- a/lib/Path/Dispatcher/Rule/Intersection.pm
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env perl
-package Path::Dispatcher::Rule::Intersection;
-use Moose;
-use MooseX::AttributeHelpers;
-
-extends 'Path::Dispatcher::Rule';
-
-has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    init_arg  => 'rules',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
-);
-
-has '+block' => (
-    required => 0,
-);
-
-sub _match {
-    my $self = shift;
-    my $path = shift;
-
-    for my $rule ($self->rules) {
-        return unless $rule->match($path);
-    }
-
-    return 1;
-}
-
-sub run {
-    my $self = shift;
-    my @rules = $self->rules;
-    for my $rule (@rules) {
-        $rule->run(@_);
-    }
-}
-
-__PACKAGE__->meta->make_immutable;
-no Moose;
-
-1;
-
diff --git a/t/012-intersection.t b/t/012-intersection.t
deleted file mode 100644
index c70b4e3..0000000
--- a/t/012-intersection.t
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use Test::More tests => 12;
-use Path::Dispatcher;
-
-my @calls;
-
-my $rule = Path::Dispatcher::Rule::Intersection->new;
-my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule($rule);
-
-my $dispatch = $dispatcher->dispatch('foobar');
-my @matches = $dispatch->matches;
-is(@matches, 1, "got a match");
-is($matches[0]->rule, $rule, "empty intersection rule matches");
-$dispatch->run;
-is_deeply([splice @calls], [], "no calls yet..");
-
-$rule->add_rule(
-    Path::Dispatcher::Rule::Regex->new(
-        regex => qr/foobar/,
-        block => sub { push @calls, 'foobar' },
-    ),
-);
-
-$dispatch = $dispatcher->dispatch('foobar');
- at matches = $dispatch->matches;
-is(@matches, 1, "got a match");
-is($matches[0]->rule, $rule, "intersection rule matches");
-$dispatch->run;
-is_deeply([splice @calls], ['foobar'], "foobar block called");
-
-$dispatch = $dispatcher->dispatch('baz');
- at matches = $dispatch->matches;
-is(@matches, 0, "no matches");
-
-$rule->add_rule(
-    Path::Dispatcher::Rule::Regex->new(
-        regex => qr/baz/,
-        block => sub { push @calls, 'baz' },
-    ),
-);
-
-$dispatch = $dispatcher->dispatch('foobar');
- at matches = $dispatch->matches;
-is(@matches, 0, "no matches, because we need to match foobar AND baz");
-
-$dispatch = $dispatcher->dispatch('baz');
- at matches = $dispatch->matches;
-is(@matches, 0, "no matches, because we need to match foobar AND baz");
-
-$dispatch = $dispatcher->dispatch('foobarbaz');
- at matches = $dispatch->matches;
-is(@matches, 1, "got a match");
-is($matches[0]->rule, $rule, "intersection rule matches");
-$dispatch->run;
-is_deeply([splice @calls], ['foobar', 'baz'], "both blocks called");
-

commit e619ac59c9a76f6df3f76cea4fd2e689c3da831e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:24:42 2008 +0000

    Consistency in the test

diff --git a/t/015-regex-prefix.t b/t/015-regex-prefix.t
index 9f6827b..572ea32 100644
--- a/t/015-regex-prefix.t
+++ b/t/015-regex-prefix.t
@@ -16,7 +16,7 @@ ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, no
 ok($rule->match('foo bar'), "prefix matches the full path");
 ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
 
-is_deeply($rule->match('foobar baz'), ["foo", "bar"], "match returns just the results");
+is_deeply($rule->match('foobar:baz'), ["foo", "bar"], "match returns just the results");
 is_deeply([$rule->_match('foobar:baz')], [
     ["foo", "bar"],
     ":baz"

commit 5a3042ec0cf60b09c80a93cb87a1dcc41af902da
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:24:55 2008 +0000

    Make the code black optional

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 3a33eb4..967ac0e 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -5,9 +5,9 @@ use Moose;
 use Path::Dispatcher::Stage;
 
 has block => (
-    is       => 'ro',
-    isa      => 'CodeRef',
-    required => 1,
+    is        => 'ro',
+    isa       => 'CodeRef',
+    predicate => 'has_block',
 );
 
 has prefix => (
@@ -45,6 +45,8 @@ sub match {
 sub run {
     my $self = shift;
 
+    die "No codeblock to run" if !$self->has_block;
+
     $self->block->(@_);
 }
 

commit 6cdf949548bdb254b029ed4c96912b1704c750a6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:39:03 2008 +0000

    Refactor: the rule returns the match, instead of returning results

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 6dba7db..91b1763 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -97,13 +97,10 @@ sub dispatch_rule {
     my $self = shift;
     my %args = @_;
 
-    my $result = $args{rule}->match($args{path})
+    my $match = $args{rule}->match($args{path})
         or return 0;
 
-    $args{dispatch}->add_match(
-        %args,
-        result => $result,
-    );
+    $args{dispatch}->add_match($match);
 
     return 1;
 }
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 17bf30b..fab6bb4 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -5,15 +5,13 @@ use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Dispatch::Match;
 
-sub match_class { 'Path::Dispatcher::Dispatch::Match' }
-
 has _matches => (
     metaclass => 'Collection::Array',
     is        => 'rw',
     isa       => 'ArrayRef[Path::Dispatcher::Dispatch::Match]',
     default   => sub { [] },
     provides  => {
-        push     => '_add_match',
+        push     => 'add_match',
         elements => 'matches',
         count    => 'has_matches',
     },
@@ -30,23 +28,6 @@ sub add_redispatches {
     }
 }
 
-sub add_match {
-    my $self = shift;
-
-    my $match;
-
-    # they pass in an already instantiated match..
-    if (@_ == 1 && blessed($_[0])) {
-        $match = shift;
-    }
-    # or they pass in args to create a match..
-    else {
-        $match = $self->match_class->new(@_);
-    }
-
-    $self->_add_match($match);
-}
-
 sub run {
     my $self = shift;
     my @args = @_;
diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Dispatch/Match.pm
index a97ee35..3a5b1cc 100644
--- a/lib/Path/Dispatcher/Dispatch/Match.pm
+++ b/lib/Path/Dispatcher/Dispatch/Match.pm
@@ -7,13 +7,13 @@ use Path::Dispatcher::Rule;
 
 has path => (
     is       => 'ro',
+    isa      => 'Str',
     required => 1,
 );
 
-has stage => (
-    is       => 'ro',
-    isa      => 'Path::Dispatcher::Stage',
-    required => 1,
+has leftover => (
+    is  => 'ro',
+    isa => 'Str',
 );
 
 has rule => (
@@ -76,7 +76,7 @@ sub run_with_number_vars {
 sub ends_dispatch {
     my $self = shift;
 
-    return $self->stage->is_qualified ? 0 : 1;
+    return 1;
 }
 
 __PACKAGE__->meta->make_immutable;
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 967ac0e..c478b87 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -2,8 +2,11 @@
 package Path::Dispatcher::Rule;
 use Moose;
 
+use Path::Dispatcher::Dispatch::Match;
 use Path::Dispatcher::Stage;
 
+sub match_class { "Path::Dispatcher::Dispatch::Match" }
+
 has block => (
     is        => 'ro',
     isa       => 'CodeRef',
@@ -23,9 +26,10 @@ sub match {
     my ($result, $leftover) = $self->_match($path);
     return unless $result;
 
+    undef $leftover if defined($leftover) && length($leftover) == 0;
+
     # if we're not matching only a prefix then require the leftover to be empty
     return if defined($leftover)
-           && length($leftover)
            && !$self->prefix;
 
     # make sure that the returned values are PLAIN STRINGS
@@ -39,7 +43,14 @@ sub match {
         }
     }
 
-    return $result;
+    my $match = $self->match_class->new(
+        path     => $path,
+        rule     => $self,
+        result   => $result,
+        defined($leftover) ? (leftover => $leftover) : (),
+    );
+
+    return $match;
 }
 
 sub run {
diff --git a/t/002-rule.t b/t/002-rule.t
index 48be78a..56e765e 100644
--- a/t/002-rule.t
+++ b/t/002-rule.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 4;
+use Test::More tests => 5;
 use Path::Dispatcher::Rule;
 
 my @calls;
@@ -16,7 +16,8 @@ my $rule = Path::Dispatcher::Rule::Regex->new(
     },
 );
 
-is_deeply([$rule->match('foobar')], [['fo', 'ob']]);
+isa_ok($rule->match('foobar'), 'Path::Dispatcher::Dispatch::Match');
+is_deeply($rule->match('foobar')->result, ['fo', 'ob']);
 is_deeply([splice @calls], [], "block not called on match");
 
 $rule->run;
diff --git a/t/004-stages.t b/t/004-stages.t
index adb803d..059386d 100644
--- a/t/004-stages.t
+++ b/t/004-stages.t
@@ -18,7 +18,11 @@ for my $stage (qw/before_on on after_on/) {
 
 $dispatcher->run('foo');
 is($calls[0], 'before_on');
-is($calls[1], 'on');
+
+TODO: {
+    local $TODO = "stages are in flux";
+    is($calls[1], 'on');
+}
 
 TODO: {
     local $TODO = "after stages not yet working";
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index a5564dd..3efc103 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -38,17 +38,17 @@ for my $stage (qw/before_on after_on/) {
 $super_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'super before_on',
-    'super on',
+#    'super on',
 #    'super after_on',
 ]);
 
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
-    'sub after_on',
-    'super before_on',
-    'super on',
-    #'super after_on',
+#    'sub after_on',
+#    'super before_on',
+#    'super on',
+#    'super after_on',
 ]);
 
 $sub_dispatcher->stage('on')->add_rule(
@@ -61,7 +61,7 @@ $sub_dispatcher->stage('on')->add_rule(
 $sub_dispatcher->run('foo');
 is_deeply([splice @calls], [
     'sub before_on',
-    'sub on',
-    #'sub after_on',
+#    'sub on',
+#    'sub after_on',
 ]);
 
diff --git a/t/014-tokens-prefix.t b/t/014-tokens-prefix.t
index 3bc020d..f57e074 100644
--- a/t/014-tokens-prefix.t
+++ b/t/014-tokens-prefix.t
@@ -14,11 +14,9 @@ my $rule = Path::Dispatcher::Rule::Tokens->new(
 
 ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
 ok($rule->match('foo bar'), "prefix matches the full path");
-ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
 
-is_deeply($rule->match('foo bar baz'), ["foo", "bar"], "match returns just the results");
-is_deeply([$rule->_match('foo bar baz')], [
-    ["foo", "bar"],
-    "baz"
-], "_match returns the results and the rest of the path");
+my $match = $rule->match('foo bar baz');
+ok($match, "prefix matches a prefix of the path");
+is_deeply($match->result, ["foo", "bar"]);
+is($match->leftover, "baz");
 
diff --git a/t/015-regex-prefix.t b/t/015-regex-prefix.t
index 572ea32..f7fa2c5 100644
--- a/t/015-regex-prefix.t
+++ b/t/015-regex-prefix.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 5;
+use Test::More tests => 6;
 use Path::Dispatcher;
 
 my @calls;
@@ -15,11 +15,10 @@ my $rule = Path::Dispatcher::Rule::Regex->new(
 ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
 ok($rule->match('foo bar'), "prefix matches the full path");
 ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
+my $match = $rule->match('foobar:baz');
 
-is_deeply($rule->match('foobar:baz'), ["foo", "bar"], "match returns just the results");
-is_deeply([$rule->_match('foobar:baz')], [
-    ["foo", "bar"],
-    ":baz"
-], "_match returns the results and the rest of the path");
+ok($match, "matched foobar:baz");
 
+is_deeply($match->result, ["foo", "bar"], "match returns just the results");
+is($match->leftover, ':baz', "leftovers");
 
diff --git a/t/101-subclass.t b/t/101-subclass.t
index b2434d8..a58ecf9 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -10,17 +10,17 @@ our @calls;
 Path::Dispatcher::Test::Framework->run('foo');
 is_deeply([splice @calls], [
     'framework before foo',
-    'framework on foo',
-    #'framework after foo',
+#    'framework on foo',
+#    'framework after foo',
 ]);
 
 Path::Dispatcher::Test::App->run('foo');
 is_deeply([splice @calls], [
     'app before foo',
-    'app after foo',
-    'framework before foo',
-    'framework on foo',
-    #'framework after foo',
+#    'app after foo',
+#    'framework before foo',
+#    'framework on foo',
+#    'framework after foo',
 ]);
 
 Path::Dispatcher::Test::App->dispatcher->stage('on')->add_rule(
@@ -35,8 +35,8 @@ Path::Dispatcher::Test::App->dispatcher->stage('on')->add_rule(
 Path::Dispatcher::Test::App->run('foo');
 is_deeply([splice @calls], [
     'app before foo',
-    'app on foo',
-    #'app after foo',
+#    'app on foo',
+#    'app after foo',
 ]);
 
 for ('Path::Dispatcher::Test::Framework', 'Path::Dispatcher::Test::App') {

commit c9e739bfab2f0b16090fe2fb42d7de39b23abe9a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:42:04 2008 +0000

    Promote Match out of Dispatch/ (1/2)

diff --git a/lib/Path/Dispatcher/Dispatch/Match.pm b/lib/Path/Dispatcher/Match.pm
similarity index 100%
rename from lib/Path/Dispatcher/Dispatch/Match.pm
rename to lib/Path/Dispatcher/Match.pm

commit 38ca4d4ab2d9c523a775d6c9f61bfe4a2beec8b2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:42:07 2008 +0000

    Promote Match out of Dispatch/ (2/2)

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index fab6bb4..4752c89 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -3,12 +3,12 @@ package Path::Dispatcher::Dispatch;
 use Moose;
 use MooseX::AttributeHelpers;
 
-use Path::Dispatcher::Dispatch::Match;
+use Path::Dispatcher::Match;
 
 has _matches => (
     metaclass => 'Collection::Array',
     is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Dispatch::Match]',
+    isa       => 'ArrayRef[Path::Dispatcher::Match]',
     default   => sub { [] },
     provides  => {
         push     => 'add_match',
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 3a5b1cc..2218f55 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -1,5 +1,5 @@
 #!/usr/bin/env perl
-package Path::Dispatcher::Dispatch::Match;
+package Path::Dispatcher::Match;
 use Moose;
 
 use Path::Dispatcher::Stage;
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index c478b87..3883991 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -2,10 +2,10 @@
 package Path::Dispatcher::Rule;
 use Moose;
 
-use Path::Dispatcher::Dispatch::Match;
+use Path::Dispatcher::Match;
 use Path::Dispatcher::Stage;
 
-sub match_class { "Path::Dispatcher::Dispatch::Match" }
+sub match_class { "Path::Dispatcher::Match" }
 
 has block => (
     is        => 'ro',
diff --git a/t/002-rule.t b/t/002-rule.t
index 56e765e..931e444 100644
--- a/t/002-rule.t
+++ b/t/002-rule.t
@@ -16,7 +16,7 @@ my $rule = Path::Dispatcher::Rule::Regex->new(
     },
 );
 
-isa_ok($rule->match('foobar'), 'Path::Dispatcher::Dispatch::Match');
+isa_ok($rule->match('foobar'), 'Path::Dispatcher::Match');
 is_deeply($rule->match('foobar')->result, ['fo', 'ob']);
 is_deeply([splice @calls], [], "block not called on match");
 

commit b26ae0a6440fc6046c60e2dc3d6d129521004152
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 09:48:42 2008 +0000

    Remove stages! They kind of muddy things for little benefit at this point

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 91b1763..eaed776 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -4,11 +4,9 @@ use Moose;
 
 our $VERSION = '0.02';
 
-use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;
 
-sub stage_class    { 'Path::Dispatcher::Stage' }
 sub dispatch_class { 'Path::Dispatcher::Dispatch' }
 
 has name => (
@@ -23,11 +21,15 @@ has name => (
     },
 );
 
-has stages => (
-    is         => 'rw',
-    isa        => 'ArrayRef[Path::Dispatcher::Stage]',
-    auto_deref => 1,
-    builder    => 'default_stages',
+has _rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    default   => sub { [] },
+    provides  => {
+        push     => 'add_rule',
+        elements => 'rules',
+    },
 );
 
 has super_dispatcher => (
@@ -36,38 +38,15 @@ has super_dispatcher => (
     predicate => 'has_super_dispatcher',
 );
 
-sub default_stages {
-    my $self = shift;
-    my $stage_class = $self->stage_class;
-
-    my $before = $stage_class->new(name => 'on', qualifier => 'before');
-    my $on     = $stage_class->new(name => 'on');
-    my $after  = $stage_class->new(name => 'on', qualifier => 'after');
-
-    return [$before, $on, $after];
-}
-
-# ugh, we should probably use IxHash..
-sub stage {
-    my $self = shift;
-    my $name = shift;
-
-    for my $stage ($self->stages) {
-        return $stage if $stage->qualified_name eq $name;
-    }
-
-    return;
-}
-
 sub dispatch {
     my $self = shift;
     my $path = shift;
 
     my $dispatch = $self->dispatch_class->new;
 
-    for my $stage ($self->stages) {
-        $self->dispatch_stage(
-            stage    => $stage,
+    for my $rule ($self->rules) {
+        $self->dispatch_rule(
+            rule     => $rule,
             dispatch => $dispatch,
             path     => $path,
         );
@@ -79,20 +58,6 @@ sub dispatch {
     return $dispatch;
 }
 
-sub dispatch_stage {
-    my $self = shift;
-    my %args = @_;
-
-    my $stage = $args{stage};
-
-    for my $rule ($stage->rules) {
-        $self->dispatch_rule(
-            %args,
-            rule => $rule,
-        );
-    }
-}
-
 sub dispatch_rule {
     my $self = shift;
     my %args = @_;
@@ -151,14 +116,14 @@ Path::Dispatcher - flexible dispatch
     use Path::Dispatcher;
     my $dispatcher = Path::Dispatcher->new;
 
-    $dispatcher->stage("on")->add_rule(
+    $dispatcher->add_rule(
         Path::Dispacher::Rule::Regex->new(
             regex => qr{^/(foo)/.*},
             block => sub { warn $1; }, # foo
         )
     );
 
-    $dispatcher->stage("on")->add_rule(
+    $dispatcher->add_rule(
         Path::Dispacher::Rule::CodeRef->new(
             matcher => sub { /^\d+$/ && $_ % 2 == 0 },
             block   => sub { warn "$_ is an even number" },
@@ -181,8 +146,6 @@ Shawn M Moore, C<< <sartak at bestpractical.com> >>
 
 =head1 BUGS
 
-C<after> substages are not yet run properly when primary stage is run.
-
 The order matches when a super dispatch is added B<will> change.
 
 Please report any bugs or feature requests to
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index bfc3753..3fdfac8 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -121,7 +121,7 @@ sub _add_rule {
         or die "I don't know how to create a rule for type $matcher";
     my $rule = $rule_creator->($matcher, $block);
 
-    $dispatcher->stage($stage)->add_rule($rule);
+    $dispatcher->add_rule($rule);
 }
 
 1;
diff --git a/lib/Path/Dispatcher/Stage.pm b/lib/Path/Dispatcher/Stage.pm
deleted file mode 100644
index 75fda5e..0000000
--- a/lib/Path/Dispatcher/Stage.pm
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env perl
-package Path::Dispatcher::Stage;
-use Moose;
-use MooseX::AttributeHelpers;
-
-use Path::Dispatcher::Rule;
-
-has name => (
-    is  => 'ro',
-    isa => 'Str',
-);
-
-has qualifier => (
-    is        => 'ro',
-    isa       => 'Str',
-    predicate => 'is_qualified',
-);
-
-has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
-);
-
-sub qualified_name {
-    my $self = shift;
-    my $name = $self->name;
-
-    return $self->qualifier . '_' . $name if $self->is_qualified;
-    return $name;
-}
-
-no Moose;
-__PACKAGE__->meta->make_immutable;
-
-1;
-
diff --git a/t/001-api.t b/t/001-api.t
index 1ba79f9..574f960 100644
--- a/t/001-api.t
+++ b/t/001-api.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
@@ -26,7 +26,7 @@ is_deeply([splice @calls], [ [] ], "finally invoked the rule block");
 $dispatcher->run('foo');
 is_deeply([splice @calls], [ [] ], "invoked the rule block on 'run'");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/(bar)/,
         block => sub { push @calls, [$1, $2] },
diff --git a/t/003-404.t b/t/003-404.t
index c31576e..9d08740 100644
--- a/t/003-404.t
+++ b/t/003-404.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
diff --git a/t/004-stages.t b/t/004-stages.t
deleted file mode 100644
index 059386d..0000000
--- a/t/004-stages.t
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use Test::More tests => 3;
-use Path::Dispatcher;
-
-my @calls;
-
-my $dispatcher = Path::Dispatcher->new;
-for my $stage (qw/before_on on after_on/) {
-    $dispatcher->stage($stage)->add_rule(
-        Path::Dispatcher::Rule::Regex->new(
-            regex => qr/foo/,
-            block => sub { push @calls, $stage },
-        ),
-    );
-}
-
-$dispatcher->run('foo');
-is($calls[0], 'before_on');
-
-TODO: {
-    local $TODO = "stages are in flux";
-    is($calls[1], 'on');
-}
-
-TODO: {
-    local $TODO = "after stages not yet working";
-    is($calls[2], 'after_on');
-}
-
diff --git a/t/005-multi-rule.t b/t/005-multi-rule.t
index 4f645ff..ad10594 100644
--- a/t/005-multi-rule.t
+++ b/t/005-multi-rule.t
@@ -8,7 +8,7 @@ my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
 for my $number (qw/first second/) {
-    $dispatcher->stage('on')->add_rule(
+    $dispatcher->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
             block => sub { push @calls, $number },
diff --git a/t/006-abort.t b/t/006-abort.t
index f3b2d67..9e68032 100644
--- a/t/006-abort.t
+++ b/t/006-abort.t
@@ -8,7 +8,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
@@ -18,7 +18,7 @@ $dispatcher->stage('on')->add_rule(
     ),
 );
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
@@ -38,7 +38,7 @@ lives_ok {
 };
 is_deeply([splice @calls], ['on'], "correctly aborted the entire dispatch");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/bar/,
         block => sub {
diff --git a/t/007-coderef-matcher.t b/t/007-coderef-matcher.t
index 470d53a..ad68f47 100644
--- a/t/007-coderef-matcher.t
+++ b/t/007-coderef-matcher.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my (@matches, @calls);
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::CodeRef->new(
         matcher => sub { push @matches, $_; length > 5 },
         block   => sub { push @calls, [@_] },
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
index 3efc103..6dfae38 100644
--- a/t/008-super-dispatcher.t
+++ b/t/008-super-dispatcher.t
@@ -18,7 +18,7 @@ ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
 is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");
 
 for my $stage (qw/before_on on after_on/) {
-    $super_dispatcher->stage($stage)->add_rule(
+    $super_dispatcher->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
             block => sub { push @calls, "super $stage" },
@@ -27,7 +27,7 @@ for my $stage (qw/before_on on after_on/) {
 }
 
 for my $stage (qw/before_on after_on/) {
-    $sub_dispatcher->stage($stage)->add_rule(
+    $sub_dispatcher->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
             block => sub { push @calls, "sub $stage" },
@@ -51,7 +51,7 @@ is_deeply([splice @calls], [
 #    'super after_on',
 ]);
 
-$sub_dispatcher->stage('on')->add_rule(
+$sub_dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, "sub on" },
diff --git a/t/009-args.t b/t/009-args.t
index ffd3bab..2a3f794 100644
--- a/t/009-args.t
+++ b/t/009-args.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { push @calls, [@_] },
diff --git a/t/010-return.t b/t/010-return.t
index e04be24..c419ba9 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 # we currently have no defined return strategy :/
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub { return @_ },
@@ -20,7 +20,7 @@ my $dispatch = $dispatcher->dispatch('foo');
 is_deeply([$dispatch->run(24)], []);
 
 for my $stage (qw/before_on on after_on/) {
-    $dispatcher->stage($stage)->add_rule(
+    $dispatcher->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
             block => sub { return @_ },
diff --git a/t/011-next-rule.t b/t/011-next-rule.t
index aee1e7d..225da20 100644
--- a/t/011-next-rule.t
+++ b/t/011-next-rule.t
@@ -8,7 +8,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
@@ -19,7 +19,7 @@ $dispatcher->stage('on')->add_rule(
     ),
 );
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {
@@ -39,7 +39,7 @@ lives_ok {
 };
 is_deeply([splice @calls], ['on', 'last'], "correctly continued to the next rule");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/bar/,
         block => sub {
diff --git a/t/013-tokens.t b/t/013-tokens.t
index ddeeab2..61516f6 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -7,7 +7,7 @@ use Path::Dispatcher;
 my @calls;
 
 my $dispatcher = Path::Dispatcher->new;
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => ['foo', 'bar'],
         block  => sub { push @calls, [$1, $2, $3] },
@@ -17,7 +17,7 @@ $dispatcher->stage('on')->add_rule(
 $dispatcher->run('foo bar');
 is_deeply([splice @calls], [ ['foo', 'bar', undef] ], "correctly populated number vars from [str, str] token rule");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => ['foo', qr/bar/],
         block  => sub { push @calls, [$1, $2, $3] },
@@ -33,7 +33,7 @@ is_deeply([splice @calls], [ ['foo', 'barbaz', undef] ], "ran the second [str, r
 $dispatcher->run('foo bar baz');
 is_deeply([splice @calls], [], "no matches");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => [["Bat", "Super"], "Man"],
         block  => sub { push @calls, [$1, $2, $3] },
@@ -49,7 +49,7 @@ is_deeply([splice @calls], [ ['Bat', 'Man', undef] ], "ran the [ [Str,Str], Str
 $dispatcher->run('Aqua Man');
 is_deeply([splice @calls], [ ], "no match");
 
-$dispatcher->stage('on')->add_rule(
+$dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => [[[[qr/Deep/]]], "Man"],
         block  => sub { push @calls, [$1, $2, $3] },
diff --git a/t/101-subclass.t b/t/101-subclass.t
index a58ecf9..e35ad24 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -23,7 +23,7 @@ is_deeply([splice @calls], [
 #    'framework after foo',
 ]);
 
-Path::Dispatcher::Test::App->dispatcher->stage('on')->add_rule(
+Path::Dispatcher::Test::App->dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
         block => sub {

commit df8d8af40883bd9c7634ba5220eed39e6dc8619e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:16:46 2008 +0000

    More explicit support for multiple matches from a rule, etc

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index eaed776..8edba1b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -62,10 +62,10 @@ sub dispatch_rule {
     my $self = shift;
     my %args = @_;
 
-    my $match = $args{rule}->match($args{path})
+    my @matches = $args{rule}->match($args{path})
         or return 0;
 
-    $args{dispatch}->add_match($match);
+    $args{dispatch}->add_matches(@matches);
 
     return 1;
 }
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 4752c89..98d3077 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -17,14 +17,15 @@ has _matches => (
     },
 );
 
+# alias add_matches -> add_match
+__PACKAGE__->meta->add_method(add_matches => __PACKAGE__->can('add_match'));
+
 sub add_redispatches {
     my $self       = shift;
     my @dispatches = @_;
 
     for my $dispatch (@dispatches) {
-        for my $match ($dispatch->matches) {
-            $self->add_match($match);
-        }
+        $self->add_matches($dispatch->matches);
     }
 }
 

commit f122016d9a4714b7304b16e288168c9c10583d58
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:17:00 2008 +0000

    Fixes for the rules attr in Path::Dispatcher

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 8edba1b..55c4870 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,6 +1,7 @@
 #!/usr/bin/env perl
 package Path::Dispatcher;
 use Moose;
+use MooseX::AttributeHelpers;
 
 our $VERSION = '0.02';
 
@@ -25,6 +26,7 @@ has _rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',
     isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    init_args => 'rules',
     default   => sub { [] },
     provides  => {
         push     => 'add_rule',

commit 5fd4b8ca51ff0815cfd76f84d37b84c641a2c06c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:17:04 2008 +0000

    "under" rules

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 3883991..89f2cb6 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -68,6 +68,7 @@ no Moose;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
+require Path::Dispatcher::Rule::Under;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
new file mode 100644
index 0000000..20e34d7
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -0,0 +1,37 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Under;
+use Moose;
+use MooseX::AttributeHelpers;
+extends 'Path::Dispatcher::Rule';
+
+has predicate => (
+    is  => 'ro',
+    isa => 'Path::Dispatcher::Rule',
+);
+
+has _rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    init_arg  => 'rules',
+    default   => sub { [] },
+    provides  => {
+        push     => 'add_rule',
+        elements => 'rules',
+    },
+);
+
+sub match {
+    my $self = shift;
+    my $path = shift;
+
+    my $prefix_match = $self->predicate->match($path)
+        or return;
+
+    my $suffix = $prefix_match->leftover;
+
+    return grep { defined } map { $_->match($suffix) } $self->rules;
+}
+
+1;
+
diff --git a/t/012-under.t b/t/012-under.t
new file mode 100644
index 0000000..21b7514
--- /dev/null
+++ b/t/012-under.t
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 9;
+use Path::Dispatcher;
+
+my @calls;
+
+my $predicate = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['ticket'],
+    prefix => 1,
+);
+
+my $create = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['create'],
+    block  => sub { push @calls, "ticket create" },
+);
+
+my $update = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['update'],
+    block  => sub { push @calls, "ticket update" },
+);
+
+my $under = Path::Dispatcher::Rule::Under->new(
+    predicate => $predicate,
+    rules     => [$create, $update],
+);
+
+my ($ticket_create) = $under->match("ticket create");
+ok($ticket_create, "matched 'ticket create'");
+

commit 0be20cea69c43664155360adeca70b827d1b45e8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:22:57 2008 +0000

    More substantial tests for under

diff --git a/t/012-under.t b/t/012-under.t
index 21b7514..1671cc1 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 9;
+use Test::More tests => 4;
 use Path::Dispatcher;
 
 my @calls;
@@ -19,6 +19,7 @@ my $create = Path::Dispatcher::Rule::Tokens->new(
 my $update = Path::Dispatcher::Rule::Tokens->new(
     tokens => ['update'],
     block  => sub { push @calls, "ticket update" },
+    prefix => 1,
 );
 
 my $under = Path::Dispatcher::Rule::Under->new(
@@ -29,3 +30,12 @@ my $under = Path::Dispatcher::Rule::Under->new(
 my ($ticket_create) = $under->match("ticket create");
 ok($ticket_create, "matched 'ticket create'");
 
+my ($fail) = $under->match("ticket create foo");
+ok(!$fail, "did not match 'ticket create' because it's not a prefix");
+
+my ($ticket_update) = $under->match("ticket update");
+ok($ticket_update, "matched 'ticket update'");
+
+my ($ticket_update_foo) = $under->match("ticket update foo");
+ok($ticket_update_foo, "matched 'ticket update foo' because it is a prefix");
+

commit 828d6e7a6683fd2b4608aab651edf5689a230a34
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:38:04 2008 +0000

    Grep out 0-length tokens

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index c67ba5d..8babb14 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -45,7 +45,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    my @tokens = split $self->delimiter, $path;
+    my @tokens = grep { length } split $self->delimiter, $path;
     my @matched;
 
     for my $expected ($self->tokens) {

commit 2dcf1ec6df84c7543d9352bd0543800ee3999c45
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 10:38:19 2008 +0000

    More under tests.. looking good.. :)

diff --git a/t/012-under.t b/t/012-under.t
index 1671cc1..6cf4a1e 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 4;
+use Test::More tests => 14;
 use Path::Dispatcher;
 
 my @calls;
@@ -13,12 +13,10 @@ my $predicate = Path::Dispatcher::Rule::Tokens->new(
 
 my $create = Path::Dispatcher::Rule::Tokens->new(
     tokens => ['create'],
-    block  => sub { push @calls, "ticket create" },
 );
 
 my $update = Path::Dispatcher::Rule::Tokens->new(
     tokens => ['update'],
-    block  => sub { push @calls, "ticket update" },
     prefix => 1,
 );
 
@@ -27,15 +25,52 @@ my $under = Path::Dispatcher::Rule::Under->new(
     rules     => [$create, $update],
 );
 
-my ($ticket_create) = $under->match("ticket create");
-ok($ticket_create, "matched 'ticket create'");
+my %tests = (
+    "ticket create" => {},
+    "ticket update" => {},
+    "  ticket   update  " => {
+        name => "whitespace doesn't matter for token-based rules",
+    },
+    "ticket update foo" => {
+        name => "'ticket update' rule is prefix",
+    },
 
-my ($fail) = $under->match("ticket create foo");
-ok(!$fail, "did not match 'ticket create' because it's not a prefix");
+    "ticket create foo" => {
+        fail => 1,
+        catchall => 1,
+        name => "did not match 'ticket create foo' because it's not a suffix",
+    },
+    "comment create" => {
+        fail => 1,
+        name => "did not match 'comment create' because the prefix is ticket",
+    },
+    "ticket delete" => {
+        fail => 1,
+        catchall => 1,
+        name => "did not match 'ticket delete' because delete is not a suffix",
+    },
+);
+
+for my $path (keys %tests) {
+    my $data = $tests{$path};
+    my $name = $data->{name} || $path;
+
+    my $match = $under->match($path);
+    $match = !$match if $data->{fail};
+    ok($match, $name);
+}
+
+my $catchall = Path::Dispatcher::Rule::Regex->new(
+    regex => qr/()/,
+);
 
-my ($ticket_update) = $under->match("ticket update");
-ok($ticket_update, "matched 'ticket update'");
+$under->add_rule($catchall);
 
-my ($ticket_update_foo) = $under->match("ticket update foo");
-ok($ticket_update_foo, "matched 'ticket update foo' because it is a prefix");
+for my $path (keys %tests) {
+    my $data = $tests{$path};
+    my $name = $data->{name} || $path;
 
+    my $match = $under->match($path);
+    $match = !$match if $data->{fail} && !$data->{catchall};
+    ok($match, $name);
+}

commit 68dc3866e3b848eb0ad588ac16e04b1f3d527c7d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:12:25 2008 +0000

    Make attributes rw

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 2218f55..cdfe755 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -6,28 +6,28 @@ use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 
 has path => (
-    is       => 'ro',
+    is       => 'rw',
     isa      => 'Str',
     required => 1,
 );
 
 has leftover => (
-    is  => 'ro',
+    is  => 'rw',
     isa => 'Str',
 );
 
 has rule => (
-    is       => 'ro',
+    is       => 'rw',
     isa      => 'Path::Dispatcher::Rule',
     required => 1,
 );
 
 has result => (
-    is => 'ro',
+    is => 'rw',
 );
 
 has set_number_vars => (
-    is      => 'ro',
+    is      => 'rw',
     isa     => 'Bool',
     lazy    => 1,
     default => sub { ref(shift->result) eq 'ARRAY' },
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 89f2cb6..027aeca 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -8,13 +8,13 @@ use Path::Dispatcher::Stage;
 sub match_class { "Path::Dispatcher::Match" }
 
 has block => (
-    is        => 'ro',
+    is        => 'rw',
     isa       => 'CodeRef',
     predicate => 'has_block',
 );
 
 has prefix => (
-    is      => 'ro',
+    is      => 'rw',
     isa     => 'Bool',
     default => 0,
 );
diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index b1abbc7..0858a52 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -4,7 +4,7 @@ use Moose;
 extends 'Path::Dispatcher::Rule';
 
 has matcher => (
-    is       => 'ro',
+    is       => 'rw',
     isa      => 'CodeRef',
     required => 1,
 );
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index 9a4f53a..4517e7a 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -4,7 +4,7 @@ use Moose;
 extends 'Path::Dispatcher::Rule';
 
 has regex => (
-    is       => 'ro',
+    is       => 'rw',
     isa      => 'RegexpRef',
     required => 1,
 );
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 8babb14..32d86b5 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -28,7 +28,7 @@ subtype 'Path::Dispatcher::Tokens'
      => as 'ArrayRef[Path::Dispatcher::Token|Path::Dispatcher::TokenAlternation]';
 
 has tokens => (
-    is         => 'ro',
+    is         => 'rw',
     isa        => 'Path::Dispatcher::Tokens',
     isa        => 'ArrayRef',
     auto_deref => 1,
@@ -36,7 +36,7 @@ has tokens => (
 );
 
 has delimiter => (
-    is      => 'ro',
+    is      => 'rw',
     isa     => 'Str',
     default => ' ',
 );
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 20e34d7..04817c8 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -5,7 +5,7 @@ use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
 has predicate => (
-    is  => 'ro',
+    is  => 'rw',
     isa => 'Path::Dispatcher::Rule',
 );
 

commit 2d77454fd59108bc33e8fa953efd65f773abfb54
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:13:19 2008 +0000

    Blocks are no longer required, so don't pass block => undef and trip the type constraint

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 3fdfac8..b0e82af 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -91,25 +91,25 @@ my %rule_creator = (
     ARRAY => sub {
         Path::Dispatcher::Rule::Tokens->new(
             tokens => $_[0],
-            block  => $_[1],
+            $_[1] ? (block => $_[1]) : (),
         ),
     },
     CODE => sub {
         Path::Dispatcher::Rule::CodeRef->new(
             matcher => $_[0],
-            block   => $_[1],
+            $_[1] ? (block => $_[1]) : (),
         ),
     },
     Regexp => sub {
         Path::Dispatcher::Rule::Regex->new(
             regex => $_[0],
-            block => $_[1],
+            $_[1] ? (block => $_[1]) : (),
         ),
     },
     '' => sub {
         Path::Dispatcher::Rule::Tokens->new(
             tokens => [ $_[0] ],
-            block  => $_[1],
+            $_[1] ? (block => $_[1]) : (),
         ),
     },
 );

commit dd1cc68ad3bdafdb12d3d2642332936e971fafd8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:13:43 2008 +0000

    Split the create/add phases of declarative rule creation

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index b0e82af..0beb4fe 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -114,14 +114,25 @@ my %rule_creator = (
     },
 );
 
-sub _add_rule {
-    my ($dispatcher, $stage, $matcher, $block) = @_;
+sub _create_rule {
+    my ($stage, $matcher, $block) = @_;
 
     my $rule_creator = $rule_creator{ ref $matcher }
         or die "I don't know how to create a rule for type $matcher";
-    my $rule = $rule_creator->($matcher, $block);
+    return $rule_creator->($matcher, $block);
+}
+
+sub _add_rule {
+    my ($dispatcher, $stage, $matcher, $block, @rest) = @_;
 
-    $dispatcher->add_rule($rule);
+    my $rule = _create_rule($stage, $matcher, $block);
+
+    if (!defined(wantarray)) {
+        $dispatcher->add_rule($rule);
+    }
+    else {
+        return $rule, @rest;
+    }
 }
 
 1;

commit beab301b73a4b80b0d6650a0cb46f4544a2eada9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:18:29 2008 +0000

    Misc incisions

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index cdfe755..b2bbeb5 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -2,7 +2,6 @@
 package Path::Dispatcher::Match;
 use Moose;
 
-use Path::Dispatcher::Stage;
 use Path::Dispatcher::Rule;
 
 has path => (
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 027aeca..bb84b63 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -3,7 +3,6 @@ package Path::Dispatcher::Rule;
 use Moose;
 
 use Path::Dispatcher::Match;
-use Path::Dispatcher::Stage;
 
 sub match_class { "Path::Dispatcher::Match" }
 
diff --git a/t/012-under.t b/t/012-under.t
index 6cf4a1e..d1dc6b2 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -4,8 +4,6 @@ use warnings;
 use Test::More tests => 14;
 use Path::Dispatcher;
 
-my @calls;
-
 my $predicate = Path::Dispatcher::Rule::Tokens->new(
     tokens => ['ticket'],
     prefix => 1,

commit 394e5ff3d4a93c47fb7ba483c1c24023130a0938
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:19:43 2008 +0000

    Initial support for "under" but it won't compose with itself so we need more syntax

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 0beb4fe..ee17e6e 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -82,6 +82,17 @@ sub build_sugar {
         after => sub {
             _add_rule($dispatcher, 'after_on', @_);
         },
+        under => sub {
+            my $predicate = _create_rule('on', shift);
+            $predicate->prefix(1);
+            my @rules = @_;
+
+            my $under = Path::Dispatcher::Rule::Under->new(
+                predicate => $predicate,
+                rules     => \@rules,
+            );
+            $dispatcher->add_rule($under);
+        },
         next_rule => sub { die "Path::Dispatcher next rule\n" },
         last_rule => sub { die "Path::Dispatcher abort\n" },
     };
diff --git a/t/016-more-under.t b/t/016-more-under.t
new file mode 100644
index 0000000..1390d09
--- /dev/null
+++ b/t/016-more-under.t
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+my @calls;
+
+do {
+    package Under::Where;
+    use Path::Dispatcher::Declarative -base;
+
+    under [ 'ticket' ] => (
+        on 'create' => sub { push @calls, "ticket create" },
+        on 'update' => sub { push @calls, "ticket update" },
+    );
+};
+
+Under::Where->run('ticket create');
+is_deeply([splice @calls], ['ticket create']);
+
+Under::Where->run('ticket update');
+is_deeply([splice @calls], ['ticket update']);
+
+Under::Where->run('ticket foo');
+is_deeply([splice @calls], []);
+

commit efc0289a268b3899668339d7d5ff48730d94db71
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:40:14 2008 +0000

    Allow passing in an already-instantiated rule to _add_rule

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index ee17e6e..b9c807e 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -134,15 +134,27 @@ sub _create_rule {
 }
 
 sub _add_rule {
-    my ($dispatcher, $stage, $matcher, $block, @rest) = @_;
+    my $dispatcher = shift;
+    my $rule;
 
-    my $rule = _create_rule($stage, $matcher, $block);
+    if (!ref($_[0])) {
+        my ($stage, $matcher, $block) = splice @_, 0, 3;
+        $rule = _create_rule($stage, $matcher, $block);
+    }
+    else {
+        $rule = shift;
+    }
 
     if (!defined(wantarray)) {
-        $dispatcher->add_rule($rule);
+        if ($UNDER_RULE) {
+            $UNDER_RULE->add_rule($rule);
+        }
+        else {
+            $dispatcher->add_rule($rule);
+        }
     }
     else {
-        return $rule, @rest;
+        return $rule, @_;
     }
 }
 

commit 0e0e627870d63233d6c61bdc53af2a00e57f2dc6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:40:40 2008 +0000

    Figure out a syntax for under rules that works well enough for v1

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index b9c807e..59006d4 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -8,6 +8,7 @@ use Sub::Exporter;
 
 our $CALLER; # Sub::Exporter doesn't make this available
 our $OUTERMOST_DISPATCHER;
+our $UNDER_RULE;
 
 my $exporter = Sub::Exporter::build_exporter({
     into_level => 1,
@@ -83,15 +84,21 @@ sub build_sugar {
             _add_rule($dispatcher, 'after_on', @_);
         },
         under => sub {
-            my $predicate = _create_rule('on', shift);
+            my ($matcher, $rules) = @_;
+
+            my $predicate = _create_rule('on', $matcher);
             $predicate->prefix(1);
-            my @rules = @_;
 
             my $under = Path::Dispatcher::Rule::Under->new(
                 predicate => $predicate,
-                rules     => \@rules,
             );
-            $dispatcher->add_rule($under);
+
+            do {
+                local $UNDER_RULE = $under;
+                $rules->();
+            };
+
+            _add_rule($dispatcher, $under, @_);
         },
         next_rule => sub { die "Path::Dispatcher next rule\n" },
         last_rule => sub { die "Path::Dispatcher abort\n" },
diff --git a/t/016-more-under.t b/t/016-more-under.t
index 1390d09..04763be 100644
--- a/t/016-more-under.t
+++ b/t/016-more-under.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 3;
+use Test::More tests => 8;
 
 my @calls;
 
@@ -9,10 +9,21 @@ do {
     package Under::Where;
     use Path::Dispatcher::Declarative -base;
 
-    under [ 'ticket' ] => (
-        on 'create' => sub { push @calls, "ticket create" },
-        on 'update' => sub { push @calls, "ticket update" },
-    );
+    under 'ticket' => sub {
+        on 'create' => sub { push @calls, "ticket create" };
+        on 'update' => sub { push @calls, "ticket update" };
+    };
+
+    under 'blog' => sub {
+        under 'post' => sub {
+            on 'create' => sub { push @calls, "create blog post" };
+            on 'delete' => sub { push @calls, "delete blog post" };
+        };
+        under 'comment' => sub {
+            on 'create' => sub { push @calls, "create blog comment" };
+            on 'delete' => sub { push @calls, "delete blog comment" };
+        };
+    };
 };
 
 Under::Where->run('ticket create');
@@ -24,3 +35,18 @@ is_deeply([splice @calls], ['ticket update']);
 Under::Where->run('ticket foo');
 is_deeply([splice @calls], []);
 
+Under::Where->run('blog');
+is_deeply([splice @calls], []);
+
+Under::Where->run('blog post');
+is_deeply([splice @calls], []);
+
+Under::Where->run('blog post create');
+is_deeply([splice @calls], ['create blog post']);
+
+Under::Where->run('blog comment');
+is_deeply([splice @calls], []);
+
+Under::Where->run('blog comment create');
+is_deeply([splice @calls], ['create blog comment']);
+

commit eb1e42075f2fee63061ab1d8fecd6cea8486e0b0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 11:46:30 2008 +0000

    Use the empty string for no leftover instead of undef

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index bb84b63..79892be 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -25,10 +25,10 @@ sub match {
     my ($result, $leftover) = $self->_match($path);
     return unless $result;
 
-    undef $leftover if defined($leftover) && length($leftover) == 0;
+    $leftover = '' if !defined($leftover);
 
     # if we're not matching only a prefix then require the leftover to be empty
-    return if defined($leftover)
+    return if length($leftover)
            && !$self->prefix;
 
     # make sure that the returned values are PLAIN STRINGS
@@ -46,7 +46,7 @@ sub match {
         path     => $path,
         rule     => $self,
         result   => $result,
-        defined($leftover) ? (leftover => $leftover) : (),
+        leftover => $leftover,
     );
 
     return $match;

commit bd62159244e0d9eeae82daf40b31c7546cc48218
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 12:04:55 2008 +0000

    Require that declarative dispatchers inherit from Path::Dispatcher::Declarative, for sanity (and feature) reasons

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 59006d4..63afd9d 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -20,22 +20,15 @@ my $exporter = Sub::Exporter::build_exporter({
 sub import {
     my $self = shift;
     my $pkg  = caller;
-    my @args = grep { !/^-[Bb]ase/ } @_;
 
-    # they must have specified '-base' if there are a different number of args
-    if (@args != @_) {
+    do {
         no strict 'refs';
         push @{ $pkg . '::ISA' }, $self;
-    }
-    else {
-        # we don't want our subclasses exporting our sugar
-        # unless the user specifies -base
-        return if $self ne __PACKAGE__;
-    }
+    };
 
     local $CALLER = $pkg;
 
-    $exporter->($self, @args);
+    $exporter->($self, @_);
 }
 
 sub build_sugar {
diff --git a/t/016-more-under.t b/t/016-more-under.t
index 04763be..7168efb 100644
--- a/t/016-more-under.t
+++ b/t/016-more-under.t
@@ -7,7 +7,7 @@ my @calls;
 
 do {
     package Under::Where;
-    use Path::Dispatcher::Declarative -base;
+    use Path::Dispatcher::Declarative;
 
     under 'ticket' => sub {
         on 'create' => sub { push @calls, "ticket create" };
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 32ef1fb..49bd6c8 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,41 +1,25 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 6;
+use Test::More tests => 3;
 
 my @calls;
 
-for my $use_base (0, 1) {
-    my $dispatcher = $use_base ? 'MyApp::Dispatcher' : 'MyApp::DispatcherBase';
+do {
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative;
 
-    # duplicated code is worse than eval!
-    my $code = "
-        package $dispatcher;
-    ";
+    on qr/(b)(ar)(.*)/ => sub {
+        push @calls, [$1, $2, $3];
+    };
 
-    $code .= 'use Path::Dispatcher::Declarative';
-    $code .= ' -base' if $use_base;
-    $code .= ';';
+};
 
-    $code .= '
-        on qr/(b)(ar)(.*)/ => sub {
-            push @calls, [$1, $2, $3];
-        };
-    ';
+ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative sets up ISA");
 
-    eval $code;
-
-    if ($use_base) {
-        ok($dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative -base sets up ISA");
-    }
-    else {
-        ok(!$dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative does NOT set up ISA");
-    }
-
-    can_ok($dispatcher => qw/dispatcher dispatch run/);
-    $dispatcher->run('foobarbaz');
-    is_deeply([splice @calls], [
-        [ 'b', 'ar', 'baz' ],
-    ]);
-}
+can_ok('MyApp::Dispatcher' => qw/dispatcher dispatch run/);
+MyApp::Dispatcher->run('foobarbaz');
+is_deeply([splice @calls], [
+    [ 'b', 'ar', 'baz' ],
+]);
 
diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
index 94a63e4..8387540 100644
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -2,7 +2,7 @@
 package Path::Dispatcher::Test::App;
 use strict;
 use warnings;
-use Path::Dispatcher::Test::Framework -base;
+use Path::Dispatcher::Test::Framework;
 
 before qr/foo/ => sub {
     push @main::calls, 'app before foo';
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
index c222c14..c7ffbcf 100644
--- a/t/lib/Path/Dispatcher/Test/Framework.pm
+++ b/t/lib/Path/Dispatcher/Test/Framework.pm
@@ -2,7 +2,7 @@
 package Path::Dispatcher::Test::Framework;
 use strict;
 use warnings;
-use Path::Dispatcher::Declarative -base;
+use Path::Dispatcher::Declarative;
 
 before qr/foo/ => sub {
     push @main::calls, 'framework before foo';

commit 2c60abe5bd52e2fc3e63f30e4a4c46dd0ea1c191
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 12:05:00 2008 +0000

    Make the helper functions in PDD methods so we can have methods that subclasses override

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 63afd9d..92fb281 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -68,18 +68,18 @@ sub build_sugar {
             $OUTERMOST_DISPATCHER->run(@_);
         },
         on => sub {
-            _add_rule($dispatcher, 'on', @_);
+            $into->_add_rule('on', @_);
         },
         before => sub {
-            _add_rule($dispatcher, 'before_on', @_);
+            $into->_add_rule('before_on', @_);
         },
         after => sub {
-            _add_rule($dispatcher, 'after_on', @_);
+            $into->_add_rule('after_on', @_);
         },
         under => sub {
             my ($matcher, $rules) = @_;
 
-            my $predicate = _create_rule('on', $matcher);
+            my $predicate = $into->_create_rule('on', $matcher);
             $predicate->prefix(1);
 
             my $under = Path::Dispatcher::Rule::Under->new(
@@ -91,7 +91,7 @@ sub build_sugar {
                 $rules->();
             };
 
-            _add_rule($dispatcher, $under, @_);
+            $into->_add_rule($under, @_);
         },
         next_rule => sub { die "Path::Dispatcher next rule\n" },
         last_rule => sub { die "Path::Dispatcher abort\n" },
@@ -100,46 +100,50 @@ sub build_sugar {
 
 my %rule_creator = (
     ARRAY => sub {
+        my ($self, $tokens, $block) = @_;
         Path::Dispatcher::Rule::Tokens->new(
-            tokens => $_[0],
-            $_[1] ? (block => $_[1]) : (),
+            tokens => $tokens,
+            $block ? (block => $block) : (),
         ),
     },
     CODE => sub {
+        my ($self, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(
-            matcher => $_[0],
-            $_[1] ? (block => $_[1]) : (),
+            matcher => $matcher,
+            $block ? (block => $block) : (),
         ),
     },
     Regexp => sub {
+        my ($self, $regex, $block) = @_;
         Path::Dispatcher::Rule::Regex->new(
-            regex => $_[0],
-            $_[1] ? (block => $_[1]) : (),
+            regex => $regex,
+            $block ? (block => $block) : (),
         ),
     },
     '' => sub {
+        my ($self, $tokens, $block) = @_;
         Path::Dispatcher::Rule::Tokens->new(
-            tokens => [ $_[0] ],
-            $_[1] ? (block => $_[1]) : (),
+            tokens => [$tokens],
+            $block ? (block => $block) : (),
         ),
     },
 );
 
 sub _create_rule {
-    my ($stage, $matcher, $block) = @_;
+    my ($self, $stage, $matcher, $block) = @_;
 
     my $rule_creator = $rule_creator{ ref $matcher }
         or die "I don't know how to create a rule for type $matcher";
-    return $rule_creator->($matcher, $block);
+    return $rule_creator->($self, $matcher, $block);
 }
 
 sub _add_rule {
-    my $dispatcher = shift;
+    my $self = shift;
     my $rule;
 
     if (!ref($_[0])) {
         my ($stage, $matcher, $block) = splice @_, 0, 3;
-        $rule = _create_rule($stage, $matcher, $block);
+        $rule = $self->_create_rule($stage, $matcher, $block);
     }
     else {
         $rule = shift;
@@ -150,7 +154,7 @@ sub _add_rule {
             $UNDER_RULE->add_rule($rule);
         }
         else {
-            $dispatcher->add_rule($rule);
+            $self->dispatcher->add_rule($rule);
         }
     }
     else {

commit 142ea3aa2055b8b75029f6f8ca317776efb9ba5e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 12:05:19 2008 +0000

    Allow dispatchers to override the token delimiter

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 92fb281..d5011b0 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -17,6 +17,8 @@ my $exporter = Sub::Exporter::build_exporter({
     },
 });
 
+sub token_delimiter { ' ' }
+
 sub import {
     my $self = shift;
     my $pkg  = caller;
@@ -103,6 +105,7 @@ my %rule_creator = (
         my ($self, $tokens, $block) = @_;
         Path::Dispatcher::Rule::Tokens->new(
             tokens => $tokens,
+            delimiter => $self->token_delimiter,
             $block ? (block => $block) : (),
         ),
     },
@@ -124,6 +127,7 @@ my %rule_creator = (
         my ($self, $tokens, $block) = @_;
         Path::Dispatcher::Rule::Tokens->new(
             tokens => [$tokens],
+            delimiter => $self->token_delimiter,
             $block ? (block => $block) : (),
         ),
     },

commit 07a848c1a5b7445b4bb198abff57c8dab11a5f61
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 13:28:08 2008 +0000

    Better error in a Rule class without _match

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 79892be..ec0f600 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -18,6 +18,8 @@ has prefix => (
     default => 0,
 );
 
+sub _match { die "_match not implemented in " . (blessed($_[0]) || $_[0]) }
+
 sub match {
     my $self = shift;
     my $path = shift;

commit 899d60e8383239df8c768808b6e043bccaaf2a47
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 13:28:11 2008 +0000

    Doc for the primary Path::Dispatcher module

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 55c4870..0b74b47 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -120,15 +120,16 @@ Path::Dispatcher - flexible dispatch
 
     $dispatcher->add_rule(
         Path::Dispacher::Rule::Regex->new(
-            regex => qr{^/(foo)/.*},
+            regex => qr{^/(foo)/},
             block => sub { warn $1; }, # foo
         )
     );
 
     $dispatcher->add_rule(
-        Path::Dispacher::Rule::CodeRef->new(
-            matcher => sub { /^\d+$/ && $_ % 2 == 0 },
-            block   => sub { warn "$_ is an even number" },
+        Path::Dispacher::Rule::Tokens->new(
+            tokens    => ['ticket', 'delete', qr/^\d+$/],
+            delimiter => '/',
+            block     => sub { delete_ticket($3) },
         )
     );
 
@@ -140,7 +141,47 @@ Path::Dispatcher - flexible dispatch
 
 We really like L<Jifty::Dispatcher> and wanted to use it for the command line.
 
-More documentation coming later, there's a lot here..
+The basic operation is that of dispatch. Dispatch takes a path and a list of
+rules, and it returns a list of matches. From there you can "run" the rules
+that matched. These phases are distinct so that, if you need to, you can
+inspect which rules were matched without ever running their codeblocks.
+
+=head1 ATTRIBUTES
+
+=head2 rules
+
+A list of L<Path::Dispatcher::Rule> objects.
+
+=head2 name
+
+A human-readable name; this will be used in the (currently nonexistent)
+debugging hooks.
+
+=head2 super_dispatcher
+
+Another Path::Dispatcher to defer to when no rules match in the current
+dispatcher. This is intended for "subclassing" dispatchers, such as when you
+have a framework dispatcher and an application dispatcher.
+
+WARNING: The super dispatcher feature is currently unstable. I'm still trying
+to figure out the right way to have them.
+
+=head1 METHODS
+
+=head2 add_rule
+
+Adds a L<Path::Dispatcher::Rule> to the end of this dispatcher's rule set.
+
+=head2 dispatch path -> dispatch
+
+Takes a string (the path) and returns a L<Path::Dispatcher::Dispatch> object
+representing a list of matches (L<Path::Dispatcher::Match> objects).
+
+=head2 run path, args
+
+Dispatches on the path and then invokes the C<run> method on the
+L<Path::Dispatcher::Dispatch> object, for when you don't need to inspect the
+dispatch.
 
 =head1 AUTHOR
 
@@ -148,8 +189,6 @@ Shawn M Moore, C<< <sartak at bestpractical.com> >>
 
 =head1 BUGS
 
-The order matches when a super dispatch is added B<will> change.
-
 Please report any bugs or feature requests to
 C<bug-path-dispatcher at rt.cpan.org>, or through the web interface at
 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Path-Dispatcher>.

commit f507c2c79618eda86b72c71fd5bce50a4d159a33
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Oct 19 13:44:29 2008 +0000

    Doc for Path::Dispatcher::Rule

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index ec0f600..30192d8 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -73,3 +73,61 @@ require Path::Dispatcher::Rule::Under;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule - predicate and codeblock
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::Regex->new(
+        regex => qr/^quit/,
+        block => sub { die "Program terminated by user.\n" },
+    );
+
+    $rule->match("die"); # undef, because "die" !~ /^quit/
+
+    my $match = $rule->match("quit"); # creates a Path::Dispatcher::Match
+
+    $rule->run; # exits the program
+
+=head1 DESCRIPTION
+
+A rule has a predicate and an optional codeblock. Rules can be matched (which
+checks the predicate against the path) and they can be ran (which invokes the
+codeblock).
+
+This class is not meant to be instantiated directly, because there is no
+predicate matching function. Instead use one of the subclasses such as
+L<Path::Dispatcher::Rule::Tokens>.
+
+=head1 ATTRIBUTES
+
+=head2 block
+
+An optional block of code to be run. Please use the C<run> method instead of
+invoking this attribute directly.
+
+=head2 prefix
+
+A boolean indicating whether this rule can match a prefix of a path. If false,
+then the predicate must match the entire path. One use-case is that you may
+want a catch-all rule that matches anything beginning with the token C<ticket>.
+The unmatched, latter part of the path will be available in the match object.
+
+=head1 METHODS
+
+=head2 match path -> match
+
+Takes a path and returns a L<Path::Dispatcher::Match> object if it matched the
+predicate, otherwise C<undef>. The match object contains information about the
+match, such as the results (e.g. for regex, a list of the captured variables),
+the C<leftover> path if C<prefix> matching was used, etc.
+
+=head2 run
+
+Runs the rule's codeblock. If none is present, it throws an exception.
+
+=cut
+

commit 02125ed039c4a0430373b43399bc396ec5d5ce7e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 11:10:07 2008 +0000

    Rule::CodeRef doc

diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index 0858a52..e230cd8 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -21,3 +21,40 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::CodeRef - predicate is any subroutine
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::CodeRef->new(
+        matcher => sub { time % 2 },
+        block => sub { warn "Odd time!" },
+    );
+
+    my $undef = $rule->match("foo"); # even time; no match :)
+
+    my $match = $rule->match("foo"); # odd time; creates a Path::Dispatcher::Match
+
+    $rule->run; # warns "Odd time!"
+
+=head1 DESCRIPTION
+
+Rules of this class can match arbitrarily complex values. This should be used
+only when there is no other recourse, because there's no way we can inspect
+how things match. Create a custom subclass of L<Path::Dispatcher::Rule> if
+necessary!
+
+=head1 ATTRIBUTES
+
+=head2 matcher
+
+A coderef that returns C<undef> if there's no match, otherwise a list of
+strings (the results).
+
+The coderef receives the path as both its one argument and C<$_>.
+
+=cut
+

commit a8d09c29c966a22bf71b14513f7553a91e62411c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 11:13:54 2008 +0000

    Rule::Regex doc

diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index 4517e7a..c1c9f39 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -31,3 +31,32 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Regex - predicate is a regular expression
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::Regex->new(
+        regex => qr{^/comment(s?)/(\d+)$},
+        block => sub { display_comment($2) },
+    );
+
+=head1 DESCRIPTION
+
+Rules of this class use a regular expression to match against the path.
+
+=head1 ATTRIBUTES
+
+=head2 regex
+
+The regular expression to match against the path. It works just as you'd expect!
+
+The results are the capture variables (C<$1>, C<$2>, etc) and when the
+resulting L<Path::Dispatcher::Match> is executed, the codeblock will see these
+values. C<$`>, C<$&>, and C<$'> are not (yet) restored.
+
+=cut
+

commit db05e103e9b02803a079e01252659b6a5388f0a1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 11:24:45 2008 +0000

    Rule::Tokens doc

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 32d86b5..72c412b 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -85,3 +85,53 @@ no Moose::Util::TypeConstraints;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Tokens - predicate is a list of tokens
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::Tokens->new(
+        tokens    => [ "comment", "show", qr/^\d+$/ ],
+        delimiter => '/',
+        block     => sub { display_comment($3) },
+    );
+
+    $rule->match("/comment/show/25");
+
+=head1 DESCRIPTION
+
+Rules of this class use a list of tokens to match the path.
+
+=head1 ATTRIBUTES
+
+=head2 tokens
+
+Each token can be a literal string, a regular expression, or a list of either
+(which are taken to mean alternations). For example, the tokens:
+
+    [ 'ticket', [ 'show', 'display' ], [ qr/^\d+$/, qr/^#\w{3}/ ] ]
+
+first matches "ticket". Then, the next token must be "show" or "display". The
+final token must be a number or a pound sign followed by three word characters.
+
+The results are the tokens in the original string, as they were matched. If you
+have three tokens, then C<$1> will be the string's first token, C<$2> its
+second, and C<$3> its third. So matching "ticket display #AAA" would have
+"ticket" in C<$1>, "display" in C<$2>, and "#AAA" in C<$3>.
+
+Capture groups inside a regex token are completely ignored.
+
+=head2 delimiter
+
+A string that is used to tokenize the path. The delimiter must be a string
+because prefix matches use C<join> on unmatched tokens to return the leftover
+path. In the future this may be extended to support having a regex delimiter.
+
+The default is a space, but if you're matching URLs you probably want to change
+this to a slash.
+
+=cut
+

commit 5fd7065c285fd4e64ae0af7d3e5f92bbe664cad3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 12:02:27 2008 +0000

    Require that the predicate of an under rule match just a prefix

diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 04817c8..8039ca7 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -1,12 +1,18 @@
 #!/usr/bin/env perl
 package Path::Dispatcher::Rule::Under;
 use Moose;
+use Moose::Util::TypeConstraints;
 use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
+subtype 'Path::Dispatcher::PrefixRule'
+     => as 'Path::Dispatcher::Rule'
+     => where { $_->prefix }
+     => message { "This rule ($_) does not match just prefixes!" };
+
 has predicate => (
     is  => 'rw',
-    isa => 'Path::Dispatcher::Rule',
+    isa => 'Path::Dispatcher::PrefixRule',
 );
 
 has _rules => (
diff --git a/t/012-under.t b/t/012-under.t
index d1dc6b2..d8cd052 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 14;
+use Test::More tests => 15;
 use Path::Dispatcher;
 
 my $predicate = Path::Dispatcher::Rule::Tokens->new(
@@ -72,3 +72,16 @@ for my $path (keys %tests) {
     $match = !$match if $data->{fail} && !$data->{catchall};
     ok($match, $name);
 }
+
+# ensure that the predicate MUST be a prefix
+eval {
+    local $SIG{__DIE__};
+
+    Path::Dispatcher::Rule::Under->new(
+        predicate => Path::Dispatcher::Rule::Tokens->new(
+            tokens => ['foo'],
+            prefix => 0,
+        ),
+    );
+};
+like($@, qr/Attribute \(predicate\) does not pass the type constraint because: This rule \(Path::Dispatcher::Rule::Tokens=HASH\(0x\w+\)\) does not match just prefixes!/, "predicate MUST match just a prefix");

commit 8b18fb0baa3a89cc5a49c3e4a5dcb83734344da5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 12:04:39 2008 +0000

    Rule::Under doc

diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 8039ca7..2cc7f9e 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -41,3 +41,55 @@ sub match {
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Under - rules under a predicate
+
+=head1 SYNOPSIS
+
+    my $ticket = Path::Dispatcher::Rule::Tokens->new(
+        tokens => [ 'ticket' ],
+        prefix => 1,
+    );
+
+    my $create = Path::Dispatcher::Rule::Tokens->new(
+        tokens => [ 'create' ],
+        block  => sub { create_ticket() },
+    );
+
+    my $delete = Path::Dispatcher::Rule::Tokens->new(
+        tokens => [ 'delete', qr/^\d+$/ ],
+        block  => sub { delete_ticket($2) },
+    );
+
+    my $rule = Path::Dispatcher::Rule::Under->new(
+        predicate => $ticket,
+        rules     => [ $create, $delete ],
+    );
+
+    $rule->match("ticket create");
+    $rule->match("ticket delete 3");
+
+=head1 DESCRIPTION
+
+Rules of this class have two-phase matching: if the predicate is matched, then
+the contained rules are matched. The benefit of this is less repetition of the
+predicate, both in terms of code and in matching it.
+
+=head1 ATTRIBUTES
+
+=head2 predicate
+
+A rule (which I<must> match prefixes) whose match determines whether the
+contained rules are considered. The leftover path of the predicate is used
+as the path for the contained rules.
+
+=head2 rules
+
+A list of rules that will be try to be matched only if the predicate is
+matched.
+
+=cut
+

commit a92dda9c984cb74f9e6708f5d5524e9f4d3508f9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 12:05:49 2008 +0000

    Make under rules immutable and remove their sugar

diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 2cc7f9e..0ae8782 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -39,6 +39,10 @@ sub match {
     return grep { defined } map { $_->match($suffix) } $self->rules;
 }
 
+__PACKAGE__->meta->make_immutable;
+no Moose;
+no Moose::Util::TypeConstraints;
+
 1;
 
 __END__

commit de283a0a00ddafe551d5e753fbe2a587561ae12e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 12:38:39 2008 +0000

    Match doc

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index b2bbeb5..bd39d27 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -83,3 +83,75 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Match - the result of a successful rule match
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::Tokens->new(
+        tokens => [ 'attack', qr/^\w+$/ ],
+        block  => sub { attack($2) },
+    );
+
+    my $match = $rule->match("attack dragon");
+
+    $match->path            # "attack dragon"
+    $match->leftover        # empty string (populated with prefix rules)
+    $match->rule            # $rule
+    $match->result          # ["attack", "dragon"] (decided by the rule)
+    $match->set_number_vars # 1 (boolean indicating whether to set $1, $2, etc)
+
+    $match->run                         # causes the player to attack the dragon
+    $match->run_with_number_vars($code) # runs $code with $1=attack $2=dragon
+
+=head1 DESCRIPTION
+
+If a L<Path::Dispatcher::Rule> successfully matches a path, it creates one or
+more C<Path::Dispatcher::Match> objects.
+
+=head1 ATTRIBUTES
+
+=head2 rule
+
+The L<Path::Dispatcher::Rule> that created this match.
+
+=head2 path
+
+The path that the rule matched.
+
+=head2 leftover
+
+The rest of the path. This is populated when the rule matches a prefix of the
+path.
+
+=head2 result
+
+Arbitrary results generated by the rule. For example, L<Path::Dispatcher::Rule::Regex> rules' result is an array reference of capture variables.
+
+=head2 set_number_vars
+
+A boolean indicating whether invoking the rule should populate the number variables (C<$1>, C<$2>, etc) with the array reference of results.
+
+Default is true if the C<result> is an array reference; otherwise false.
+
+=head1 METHODS
+
+=head2 run
+
+Executes the rule's codeblock with the same arguments. If L</set_number_vars>
+is true, then L</run_with_number_vars> is used, otherwise the rule's codeblock
+is invoked directly.
+
+=head2 run_with_number_vars coderef, $1, $2, ...
+
+Populates the number variables C<$1>, C<$2>, ... then executes the coderef.
+
+Unfortunately, the only way to achieve this (pre-5.10 anyway) is to match a
+regular expression. Both a string and a regex are constructed such that
+matching will produce the correct capture variables.
+
+=cut
+

commit e225e9de771ed137d92ad687bf4f8988552f9ef3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 14:50:43 2008 +0000

    Dispatch doc, that covers all of the classes

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 98d3077..7bd7cb3 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -60,3 +60,47 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Dispatch - a list of matches
+
+=head1 SYNOPSIS
+
+    my $dispatcher = Path::Dispatcher->new(
+        rules => [
+            Path::Dispatcher::Rule::Tokens->new(
+                tokens => [ 'attack', qr/^\w+$/ ],
+                block  => sub { attack($2) },
+            ),
+        ],
+    );
+
+    my $dispatch = $dispatcher->dispatch("attack goblin");
+
+    $dispatch->matches;     # list of matches (in this case, one)
+    $dispatch->has_matches; # whether there were any matches
+
+    $dispatch->run; # attacks the goblin
+
+=head1 DESCRIPTION
+
+Dispatching creates a C<dispatch> which is little more than a (possibly empty!)
+list of matches.
+
+=head1 ATTRIBUTES
+
+=head2 matches
+
+The list of L<Path::Dispatcher::Match> that correspond to the rules that were
+matched.
+
+=head1 METHODS
+
+=head2 run
+
+Executes matches until a match's C<ends_dispatch> returns true.
+
+=cut
+

commit e93351ea9cfa11e8d0f246721191b659604d9e7e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 18:10:32 2008 +0000

    Changes

diff --git a/Changes b/Changes
index be40a43..5d71587 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,25 @@
 Revision history for Path-Dispatcher
 
-0.01    Wed Aug 27 11:04:18 2008
+0.02 Mon Oct 20 14:10:12 2008
+        Documentation!
+        Prefix matches
+        "under" rules
+        Remove stages until they're actually needed
+
+        Rule:
+            The codeblock is no longer required
+            Empty tokens are ignored
+            The token attribute is now 'delimiter' instead of 'splitter'
+            Allow regexes and alternations in the token rules
+
+        Match:
+            No longer Dispatch::Match
+            Created by the Rule instead of the Dispatcher
+
+        Declarative:
+            You now must subclass Path::Dispatcher::Declarative
+            Allow overriding the token delimiter
+
+0.01 Wed Aug 27 11:04:18 2008
         Initial release
 

commit d9371bb4c9c8aca3a3d4e6751edcda8febf5dacb
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 18:25:59 2008 +0000

    Basic doc for Declarative

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index d5011b0..18d16a5 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -168,3 +168,80 @@ sub _add_rule {
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Declarative - sugary dispatcher
+
+=head1 SYNOPSIS
+
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative;
+
+    on score => sub { show_score() };
+    
+    on ['wield', qr/^\w+$/] => sub { wield_weapon($2) };
+
+    under display => sub {
+        on inventory => sub { show_inventory() };
+        on score     => sub { show_score() };
+    };
+
+    package Interpreter;
+    MyApp::Dispatcher->run($input);
+
+=head1 DESCRIPTION
+
+=head1 KEYWORDS
+
+=head2 dispatcher -> Dispatcher
+
+Returns the L<Path::Dispatcher> object for this class; the object that the
+sugar is modifying. This is useful for adding custom rules through the regular
+API, and inspection.
+
+=head2 dispatch path -> Dispatch
+
+Invokes the dispatcher on the given path and returns a
+L<Path::Dispatcher::Dispatch> object. Acts as a keyword within the same
+package; otherwise as a method (since these declarative dispatchers are
+supposed to be used by other packages).
+
+=head2 run path, args
+
+Performs a dispatch then invokes the L<Path::Dispatcher::Dispatch/run> method
+on it.
+
+=head2 on path => sub {}
+
+Adds a rule to the dispatcher for the given path. The path may be:
+
+=over 4
+
+=item a string
+
+This is taken to mean a single token; creates an
+L<Path::Dispatcher::Rule::Token> rule.
+
+=item an array reference
+
+This is creates a L<Path::Dispatcher::Rule::Token> rule.
+
+=item a regular expression
+
+This is creates a L<Path::Dispatcher::Rule::Regex> rule.
+
+=item a code reference
+
+This is creates a L<Path::Dispatcher::Rule::CodeRef> rule.
+
+=back
+
+=head2 under path => sub {}
+
+Creates a L<Path::Dispatcher::Rule::Under> rule. The contents of the coderef
+should be other L</on> and C<under> calls.
+
+=cut
+

commit 408c01caef44dcb39a172912e308f31dc5fe1133
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Mon Oct 20 18:27:55 2008 +0000

    Bump to 0.03

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 0b74b47..358386a 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,7 +3,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 96bbe58ed074bebf0cc3a68f2d1c5b8f5f1f8987
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 11:17:50 2008 +0000

    Typo fixes in the doc

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 358386a..959cf2d 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -119,14 +119,14 @@ Path::Dispatcher - flexible dispatch
     my $dispatcher = Path::Dispatcher->new;
 
     $dispatcher->add_rule(
-        Path::Dispacher::Rule::Regex->new(
+        Path::Dispatcher::Rule::Regex->new(
             regex => qr{^/(foo)/},
             block => sub { warn $1; }, # foo
         )
     );
 
     $dispatcher->add_rule(
-        Path::Dispacher::Rule::Tokens->new(
+        Path::Dispatcher::Rule::Tokens->new(
             tokens    => ['ticket', 'delete', qr/^\d+$/],
             delimiter => '/',
             block     => sub { delete_ticket($3) },

commit 36a773dc97c4abb1bdf505bfcb571af3602d395d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 11:34:14 2008 +0000

    Factor out (un)tokenization into methods

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 72c412b..a820831 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -45,7 +45,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    my @tokens = grep { length } split $self->delimiter, $path;
+    my @tokens = $self->tokenize($path);
     my @matched;
 
     for my $expected ($self->tokens) {
@@ -55,7 +55,7 @@ sub _match {
         push @matched, $got;
     }
 
-    my $leftover = join $self->delimiter, @tokens;
+    my $leftover = $self->untokenize(@tokens);
     return \@matched, $leftover;
 }
 
@@ -79,6 +79,18 @@ sub _match_token {
     return 0;
 }
 
+sub tokenize {
+    my $self = shift;
+    my $path = shift;
+    return grep { length } split $self->delimiter, $path;
+}
+
+sub untokenize {
+    my $self   = shift;
+    my @tokens = @_;
+    return join $self->delimiter, @tokens;
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 no Moose::Util::TypeConstraints;

commit bad3698dd7e498d726e7a99e1c3ca535f5db0c55
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:00:54 2008 +0000

    Require "-base" because otherwise "use MyApp::Dispatcher" fails when we just want to use it

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 18d16a5..4161fed 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -23,6 +23,11 @@ sub import {
     my $self = shift;
     my $pkg  = caller;
 
+    my @args = grep { !/^-[bB]ase$/ } @_;
+
+    # just loading the class..
+    return if @args == @_;
+
     do {
         no strict 'refs';
         push @{ $pkg . '::ISA' }, $self;
@@ -30,7 +35,7 @@ sub import {
 
     local $CALLER = $pkg;
 
-    $exporter->($self, @_);
+    $exporter->($self, @args);
 }
 
 sub build_sugar {
diff --git a/t/016-more-under.t b/t/016-more-under.t
index 7168efb..04763be 100644
--- a/t/016-more-under.t
+++ b/t/016-more-under.t
@@ -7,7 +7,7 @@ my @calls;
 
 do {
     package Under::Where;
-    use Path::Dispatcher::Declarative;
+    use Path::Dispatcher::Declarative -base;
 
     under 'ticket' => sub {
         on 'create' => sub { push @calls, "ticket create" };
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 49bd6c8..9c3349a 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -7,7 +7,7 @@ my @calls;
 
 do {
     package MyApp::Dispatcher;
-    use Path::Dispatcher::Declarative;
+    use Path::Dispatcher::Declarative -base;
 
     on qr/(b)(ar)(.*)/ => sub {
         push @calls, [$1, $2, $3];
diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
index 8387540..94a63e4 100644
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -2,7 +2,7 @@
 package Path::Dispatcher::Test::App;
 use strict;
 use warnings;
-use Path::Dispatcher::Test::Framework;
+use Path::Dispatcher::Test::Framework -base;
 
 before qr/foo/ => sub {
     push @main::calls, 'app before foo';
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
index c7ffbcf..c222c14 100644
--- a/t/lib/Path/Dispatcher/Test/Framework.pm
+++ b/t/lib/Path/Dispatcher/Test/Framework.pm
@@ -2,7 +2,7 @@
 package Path::Dispatcher::Test::Framework;
 use strict;
 use warnings;
-use Path::Dispatcher::Declarative;
+use Path::Dispatcher::Declarative -base;
 
 before qr/foo/ => sub {
     push @main::calls, 'framework before foo';

commit 3dd1f78b15a22aee2161b918221964f48b9f02f9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:29:14 2008 +0000

    Remove super dispatchers, redispatching will simply be a rule type

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 959cf2d..d45905b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -34,12 +34,6 @@ has _rules => (
     },
 );
 
-has super_dispatcher => (
-    is        => 'rw',
-    isa       => 'Path::Dispatcher',
-    predicate => 'has_super_dispatcher',
-);
-
 sub dispatch {
     my $self = shift;
     my $path = shift;
@@ -54,9 +48,6 @@ sub dispatch {
         );
     }
 
-    $dispatch->add_redispatches($self->redispatches($path))
-        if $self->can_redispatch;
-
     return $dispatch;
 }
 
@@ -72,15 +63,6 @@ sub dispatch_rule {
     return 1;
 }
 
-sub can_redispatch { shift->has_super_dispatcher }
-
-sub redispatches {
-    my $self = shift;
-    my $path = shift;
-
-    return $self->super_dispatcher->dispatch($path)
-}
-
 sub run {
     my $self = shift;
     my $path = shift;
@@ -157,15 +139,6 @@ A list of L<Path::Dispatcher::Rule> objects.
 A human-readable name; this will be used in the (currently nonexistent)
 debugging hooks.
 
-=head2 super_dispatcher
-
-Another Path::Dispatcher to defer to when no rules match in the current
-dispatcher. This is intended for "subclassing" dispatchers, such as when you
-have a framework dispatcher and an application dispatcher.
-
-WARNING: The super dispatcher feature is currently unstable. I'm still trying
-to figure out the right way to have them.
-
 =head1 METHODS
 
 =head2 add_rule
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 4161fed..cb7367e 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -47,11 +47,6 @@ sub build_sugar {
         name => $into,
     );
 
-    # if this is a subclass, then we want to set up a super dispatcher
-    if ($class ne __PACKAGE__) {
-        $dispatcher->super_dispatcher($class->dispatcher);
-    }
-
     return {
         dispatcher => sub { $dispatcher },
         dispatch   => sub {
diff --git a/t/008-super-dispatcher.t b/t/008-super-dispatcher.t
deleted file mode 100644
index 6dfae38..0000000
--- a/t/008-super-dispatcher.t
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use Test::More tests => 7;
-use Path::Dispatcher;
-
-my @calls;
-
-my $super_dispatcher = Path::Dispatcher->new;
-my $sub_dispatcher   = Path::Dispatcher->new(
-    super_dispatcher => $super_dispatcher,
-);
-
-isnt($super_dispatcher->name, $sub_dispatcher->name, "two dispatchers have separate names");
-
-ok(!$super_dispatcher->has_super_dispatcher, "no super dispatcher by default");
-ok($sub_dispatcher->has_super_dispatcher, "sub dispatcher has a super");
-is($sub_dispatcher->super_dispatcher, $super_dispatcher, "the super dispatcher is correct");
-
-for my $stage (qw/before_on on after_on/) {
-    $super_dispatcher->add_rule(
-        Path::Dispatcher::Rule::Regex->new(
-            regex => qr/foo/,
-            block => sub { push @calls, "super $stage" },
-        ),
-    );
-}
-
-for my $stage (qw/before_on after_on/) {
-    $sub_dispatcher->add_rule(
-        Path::Dispatcher::Rule::Regex->new(
-            regex => qr/foo/,
-            block => sub { push @calls, "sub $stage" },
-        ),
-    );
-}
-
-$super_dispatcher->run('foo');
-is_deeply([splice @calls], [
-    'super before_on',
-#    'super on',
-#    'super after_on',
-]);
-
-$sub_dispatcher->run('foo');
-is_deeply([splice @calls], [
-    'sub before_on',
-#    'sub after_on',
-#    'super before_on',
-#    'super on',
-#    'super after_on',
-]);
-
-$sub_dispatcher->add_rule(
-    Path::Dispatcher::Rule::Regex->new(
-        regex => qr/foo/,
-        block => sub { push @calls, "sub on" },
-    ),
-);
-
-$sub_dispatcher->run('foo');
-is_deeply([splice @calls], [
-    'sub before_on',
-#    'sub on',
-#    'sub after_on',
-]);
-

commit 2286bafd7786936868236cd6e1a9b89dcb4a5e8e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:34:35 2008 +0000

    Remove add_redispatch

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 7bd7cb3..acbd6a8 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -20,15 +20,6 @@ has _matches => (
 # alias add_matches -> add_match
 __PACKAGE__->meta->add_method(add_matches => __PACKAGE__->can('add_match'));
 
-sub add_redispatches {
-    my $self       = shift;
-    my @dispatches = @_;
-
-    for my $dispatch (@dispatches) {
-        $self->add_matches($dispatch->matches);
-    }
-}
-
 sub run {
     my $self = shift;
     my @args = @_;

commit f2d9a1f4c4ff98b091ab527b4d9cba85d6a2cff2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:34:38 2008 +0000

    Path::Dispatcher::Rule::Dispatch which is a much saner and more flexible "super dispatcher"

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 30192d8..00c1c2e 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -70,6 +70,7 @@ require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
 require Path::Dispatcher::Rule::Under;
+require Path::Dispatcher::Rule::Dispatch;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
new file mode 100644
index 0000000..6b42cb9
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Dispatch;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+has dispatcher => (
+    is       => 'rw',
+    isa      => 'Path::Dispatcher',
+    required => 1,
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    my $dispatch = $self->dispatcher->dispatch($path);
+    return $dispatch->matches;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+no Moose::Util::TypeConstraints;
+
+1;
+

commit 931afce19ee6aebc262f40eeeb60120717004adc
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:36:25 2008 +0000

    "match" is what returns the PD::Match objects, not "_match"

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 6b42cb9..957b145 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -9,7 +9,7 @@ has dispatcher => (
     required => 1,
 );
 
-sub _match {
+sub match {
     my $self = shift;
     my $path = shift;
 

commit 4afd976a2324eb45b789375e3f4f680de0fee783
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 14:36:27 2008 +0000

    For now we need to inform the dispatcher of the super dispatcher, hopefully this can become automatic in the future

diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
index 94a63e4..28aa5a7 100644
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ b/t/lib/Path/Dispatcher/Test/App.pm
@@ -41,5 +41,11 @@ on qr/ar(g)s/ => sub {
     next_rule;
 };
 
+__PACKAGE__->dispatcher->add_rule(
+    Path::Dispatcher::Rule::Dispatch->new(
+        dispatcher => Path::Dispatcher::Test::Framework->dispatcher,
+    )
+);
+
 1;
 

commit 1d6272b9e90e3ad2727e733fcc9e3f04acff55db
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 19:26:55 2008 +0000

    Don't settle for matching a prefix if the tokens rule does not want to be prefix matched

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index a820831..6eb3b7c 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -55,6 +55,8 @@ sub _match {
         push @matched, $got;
     }
 
+    return if @tokens && !$self->prefix;
+
     my $leftover = $self->untokenize(@tokens);
     return \@matched, $leftover;
 }

commit 930c4d8549d58b4be10761cfade4af547492ea9b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 19:45:33 2008 +0000

    Rename "run" to "redispatch"

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index cb7367e..5f3cd77 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -59,7 +59,7 @@ sub build_sugar {
 
             $OUTERMOST_DISPATCHER->dispatch(@_);
         },
-        run => sub {
+        redispatch => sub {
             # if caller is $into, then this function is being used as sugar
             # otherwise, it's probably a method call, so discard the invocant
             shift if caller ne $into;
@@ -208,7 +208,7 @@ L<Path::Dispatcher::Dispatch> object. Acts as a keyword within the same
 package; otherwise as a method (since these declarative dispatchers are
 supposed to be used by other packages).
 
-=head2 run path, args
+=head2 redispatch path, args
 
 Performs a dispatch then invokes the L<Path::Dispatcher::Dispatch/run> method
 on it.

commit 4c46dae863e194c99882f437b3ac204469e9793e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 21:08:40 2008 +0000

    Add "rewrite" sugar

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 5f3cd77..9a22493 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -69,6 +69,16 @@ sub build_sugar {
 
             $OUTERMOST_DISPATCHER->run(@_);
         },
+        rewrite => sub {
+            my ($from, $to) = @_;
+            my $rewrite = sub {
+                local $OUTERMOST_DISPATCHER = $dispatcher
+                    if !$OUTERMOST_DISPATCHER;
+                my $path = ref($to) eq 'CODE' ? $to->() : $to;
+                $OUTERMOST_DISPATCHER->run($path);
+            };
+            $into->_add_rule('on', $from, $rewrite);
+        },
         on => sub {
             $into->_add_rule('on', @_);
         },
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 9c3349a..accfb99 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 3;
+use Test::More tests => 5;
 
 my @calls;
 
@@ -13,6 +13,8 @@ do {
         push @calls, [$1, $2, $3];
     };
 
+    rewrite quux => 'bar';
+    rewrite qr/^quux-(.*)/ => sub { "bar:$1" };
 };
 
 ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative sets up ISA");
@@ -23,3 +25,13 @@ is_deeply([splice @calls], [
     [ 'b', 'ar', 'baz' ],
 ]);
 
+MyApp::Dispatcher->run('quux');
+is_deeply([splice @calls], [
+    [ 'b', 'ar', '' ],
+]);
+
+MyApp::Dispatcher->run('quux-hello');
+is_deeply([splice @calls], [
+    [ 'b', 'ar', ':hello' ],
+]);
+

commit abfcbcb27552fe83ef07fe2126a6da8100fc9023
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 21 21:09:48 2008 +0000

    Pass on args

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 9a22493..cc5de49 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -75,7 +75,7 @@ sub build_sugar {
                 local $OUTERMOST_DISPATCHER = $dispatcher
                     if !$OUTERMOST_DISPATCHER;
                 my $path = ref($to) eq 'CODE' ? $to->() : $to;
-                $OUTERMOST_DISPATCHER->run($path);
+                $OUTERMOST_DISPATCHER->run($path, @_);
             };
             $into->_add_rule('on', $from, $rewrite);
         },

commit 11923579a14aa15f53dfa440a432fcbcfff57b98
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 14:22:49 2008 +0000

    Rename redispatch back to run, it's not worth having separate

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index cc5de49..6df6241 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -59,7 +59,7 @@ sub build_sugar {
 
             $OUTERMOST_DISPATCHER->dispatch(@_);
         },
-        redispatch => sub {
+        run => sub {
             # if caller is $into, then this function is being used as sugar
             # otherwise, it's probably a method call, so discard the invocant
             shift if caller ne $into;
@@ -218,7 +218,7 @@ L<Path::Dispatcher::Dispatch> object. Acts as a keyword within the same
 package; otherwise as a method (since these declarative dispatchers are
 supposed to be used by other packages).
 
-=head2 redispatch path, args
+=head2 run path, args
 
 Performs a dispatch then invokes the L<Path::Dispatcher::Dispatch/run> method
 on it.

commit ce2e908330acac752c3fadf7760d2a1d5d94c1f5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 21:40:17 2008 +0000

    Add a rewrite example to the PDD synopsis

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 6df6241..dc96792 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -193,6 +193,8 @@ Path::Dispatcher::Declarative - sugary dispatcher
     
     on ['wield', qr/^\w+$/] => sub { wield_weapon($2) };
 
+    rewrite qr/^inv/ => "display inventory";
+
     under display => sub {
         on inventory => sub { show_inventory() };
         on score     => sub { show_score() };

commit b7ca6b042315ddb96a594fbd2c53eef6478efebe
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 21:50:29 2008 +0000

    Changes

diff --git a/Changes b/Changes
index 5d71587..dc0c155 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,19 @@
 Revision history for Path-Dispatcher
 
+0.03 Tue Oct 28 17:42:47 2008
+        "rewrite" rules
+        Super dispatchers are GONE! Yay.
+        New Rule::Dispatch which just takes some other dispatcher. nothingmuch++
+
+        Rule::Tokens:
+            Fix for always matching just a prefix whether you wanted to or not
+            Make tokenization and untokenization into methods for overriding
+
+        Declarative:
+            -base is required, otherwise unrelated modules using your dispatcher
+                would get their @ISA set!
+
+
 0.02 Mon Oct 20 14:10:12 2008
         Documentation!
         Prefix matches

commit ce9d6bce6a9a7876531329787533e2128ed0dd9c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 21:50:32 2008 +0000

    Prior art etc :)

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index d45905b..e42ded7 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -128,6 +128,9 @@ rules, and it returns a list of matches. From there you can "run" the rules
 that matched. These phases are distinct so that, if you need to, you can
 inspect which rules were matched without ever running their codeblocks.
 
+Most consumers would want to use L<Path::Dispatcher::Declarative> which gives
+you some sugar inspired by L<Jifty::Dispatcher>.
+
 =head1 ATTRIBUTES
 
 =head2 rules
@@ -166,6 +169,20 @@ Please report any bugs or feature requests to
 C<bug-path-dispatcher at rt.cpan.org>, or through the web interface at
 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Path-Dispatcher>.
 
+=head1 SEE ALSO
+
+=over 4
+
+=item L<Jifty::Dispatcher>
+
+=item L<Catalyst::Dispatcher>
+
+=item L<Mojolicious::Dispatcher>
+
+=item L<Path::Router>
+
+=back
+
 =head1 COPYRIGHT & LICENSE
 
 Copyright 2008 Best Practical Solutions.

commit dbb17cd10568d17fe8cb48e4866530f28d14cfb2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 21:56:30 2008 +0000

    Bump to 0.04

diff --git a/Changes b/Changes
index dc0c155..f0895c2 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.04
+
 0.03 Tue Oct 28 17:42:47 2008
         "rewrite" rules
         Super dispatchers are GONE! Yay.
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index e42ded7..b8d1c22 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,7 +3,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.03';
+our $VERSION = '0.04';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 4a28bcb1a0b21e94cd2b00c94edfe940a336ddae
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 21:58:04 2008 +0000

    Oops, forgot to regenerate the signature

diff --git a/Changes b/Changes
index f0895c2..f1df49c 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 Revision history for Path-Dispatcher
 
-0.04
+0.04 Tue Oct 28 17:56:41 2008
+        Dist fixes
 
 0.03 Tue Oct 28 17:42:47 2008
         "rewrite" rules

commit 65e3bbb8728a7843c4053e43d6945b2b17aa0895
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Oct 28 22:10:56 2008 +0000

    Bump to 0.05

diff --git a/Changes b/Changes
index f1df49c..eba743a 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.05
+
 0.04 Tue Oct 28 17:56:41 2008
         Dist fixes
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index b8d1c22..a9bfd0f 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,7 +3,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.04';
+our $VERSION = '0.05';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 2eefa3667ee456e719a7637c900c4c473a20efc2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Oct 29 23:34:59 2008 +0000

    Skeleton of the Rule::Dispatch doc

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 957b145..d5a0fd7 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -23,3 +23,43 @@ no Moose::Util::TypeConstraints;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Dispatch - redispatch
+
+=head1 SYNOPSIS
+
+    my $dispatcher = Path::Dispatcher->new(
+        rules => [
+            Path::Dispatcher::Rule::Tokens->new(
+                tokens => [  ],
+                block  => sub {  },
+            ),
+            Path::Dispatcher::Rule::Tokens->new(
+                tokens => [  ],
+                block  => sub {  },
+            ),
+        ],
+    );
+
+    my $rule = Path::Dispatcher::Rule::Dispatch->new(
+        dispatcher => $dispatcher,
+    );
+
+    $rule->run("");
+
+=head1 DESCRIPTION
+
+Rules of this class use another dispatcher to match the path.
+
+=head1 ATTRIBUTES
+
+=head2 dispatcher
+
+A L<Path::Dispatcher> object. Its matches will be returned by matching this
+rule.
+
+=cut
+

commit 1c04b1cad6e9fb293e90d2ef0558579af26eb06f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 00:56:54 2008 +0000

    It's called init_arg not init_args

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index a9bfd0f..37f26aa 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -26,7 +26,7 @@ has _rules => (
     metaclass => 'Collection::Array',
     is        => 'rw',
     isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    init_args => 'rules',
+    init_arg  => 'rules',
     default   => sub { [] },
     provides  => {
         push     => 'add_rule',

commit 4a3511aed11b848c952e915f454e71deed061f6c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:01:00 2008 +0000

    Pass through return values

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 37f26aa..4c9ae2e 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -68,9 +68,7 @@ sub run {
     my $path = shift;
     my $dispatch = $self->dispatch($path);
 
-    $dispatch->run(@_);
-
-    return;
+    return $dispatch->run(@_);
 }
 
 # We don't export anything, so if they request something, then try to error
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index bd39d27..bc8ea1f 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -39,14 +39,13 @@ sub run {
     local $_ = $self->path;
 
     if ($self->set_number_vars) {
-        $self->run_with_number_vars(
+        return $self->run_with_number_vars(
             sub { $self->rule->run(@args) },
             @{ $self->result },
         );
     }
-    else {
-        $self->rule->run(@args);
-    }
+
+    return $self->rule->run(@args);
 }
 
 sub run_with_number_vars {

commit 62eab31bac5dcc5cee13cd8da4ff23c1a37db9f7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:01:02 2008 +0000

    Each match gets a scalar result

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index acbd6a8..a5b4276 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -24,26 +24,28 @@ sub run {
     my $self = shift;
     my @args = @_;
     my @matches = $self->matches;
+    my @results;
 
     while (my $match = shift @matches) {
         eval {
             local $SIG{__DIE__} = 'DEFAULT';
 
-            $match->run(@args);
+            push @results, scalar $match->run(@args);
 
             die "Path::Dispatcher abort\n"
                 if $match->ends_dispatch($self);
         };
 
         if ($@) {
-            return if $@ =~ /^Path::Dispatcher abort\n/;
+            last if $@ =~ /^Path::Dispatcher abort\n/;
             next if $@ =~ /^Path::Dispatcher next rule\n/;
 
             die $@;
         }
     }
 
-    return;
+    return @results if wantarray;
+    return $results[0];
 }
 
 __PACKAGE__->meta->make_immutable;

commit 7dfb078bcbe959641ae393a2c2806926da29e7c7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:06:38 2008 +0000

    Add tests for run returning values

diff --git a/t/004-run.t b/t/004-run.t
new file mode 100644
index 0000000..88e463f
--- /dev/null
+++ b/t/004-run.t
@@ -0,0 +1,34 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher;
+
+my $dispatcher = Path::Dispatcher->new(
+    rules => [
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => ['foo'],
+            block  => sub { "foo matched" },
+        ),
+    ],
+);
+
+my $result = $dispatcher->run("foo");
+is($result, "foo matched");
+
+$dispatcher->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => ['foo', 'bar'],
+        block  => sub { "foobar matched" },
+    ),
+);
+
+$result = $dispatcher->run("foo bar");
+is($result, "foobar matched");
+
+$result = $dispatcher->run("foo");
+is($result, "foo matched");
+
+my @results = $dispatcher->run("foo");
+is_deeply(\@results, ["foo matched", "foobar matched"]);
+

commit c59752fe98e076d9b248ba84ecb5a905992338c1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:06:40 2008 +0000

    Test fixes

diff --git a/t/010-return.t b/t/010-return.t
index c419ba9..b87f271 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -10,26 +10,26 @@ my $dispatcher = Path::Dispatcher->new;
 $dispatcher->add_rule(
     Path::Dispatcher::Rule::Regex->new(
         regex => qr/foo/,
-        block => sub { return @_ },
+        block => sub { "foo" },
     ),
 );
 
-is_deeply([$dispatcher->run('foo', 42)], []);
+is_deeply([$dispatcher->run('foo', 42)], ["foo"]);
 
 my $dispatch = $dispatcher->dispatch('foo');
-is_deeply([$dispatch->run(24)], []);
+is_deeply([$dispatch->run(24)], ["foo"]);
 
 for my $stage (qw/before_on on after_on/) {
     $dispatcher->add_rule(
         Path::Dispatcher::Rule::Regex->new(
             regex => qr/foo/,
-            block => sub { return @_ },
+            block => sub { $stage },
         ),
     );
 }
 
-is_deeply([$dispatcher->run('foo', 42)], []);
+is_deeply([$dispatcher->run('foo', 42)], ["foo"]);
 
 $dispatch = $dispatcher->dispatch('foo');
-is_deeply([$dispatch->run(24)], []);
+is_deeply([$dispatch->run(24)], ["foo"]);
 

commit fb323834a431fff536edd0bc32465eec52edae4b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:19:26 2008 +0000

    Turn ends_dispatch into an attribute

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index a5b4276..1f53f6a 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -33,7 +33,7 @@ sub run {
             push @results, scalar $match->run(@args);
 
             die "Path::Dispatcher abort\n"
-                if $match->ends_dispatch($self);
+                if $match->ends_dispatch;
         };
 
         if ($@) {
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index bc8ea1f..98eb23a 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -32,6 +32,14 @@ has set_number_vars => (
     default => sub { ref(shift->result) eq 'ARRAY' },
 );
 
+# If we're a before/after (qualified) rule, then yeah, we want to continue
+# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
+has ends_dispatch => (
+    is      => 'rw',
+    isa     => 'Bool',
+    default => 1,
+);
+
 sub run {
     my $self = shift;
     my @args = @_;
@@ -69,14 +77,6 @@ sub run_with_number_vars {
     $code->();
 }
 
-# If we're a before/after (qualified) rule, then yeah, we want to continue
-# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
-sub ends_dispatch {
-    my $self = shift;
-
-    return 1;
-}
-
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit 5931e2f81d01cf318a3303dbcebc706b2d6f0dcb
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:19:32 2008 +0000

    Make tests work!

diff --git a/t/004-run.t b/t/004-run.t
index 88e463f..02558a5 100644
--- a/t/004-run.t
+++ b/t/004-run.t
@@ -1,34 +1,39 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 4;
 use Path::Dispatcher;
 
 my $dispatcher = Path::Dispatcher->new(
     rules => [
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => ['foo'],
-            block  => sub { "foo matched" },
+        Path::Dispatcher::Rule::Regex->new(
+            regex => qr/^foobar/,
+            block => sub { "foobar matched" },
         ),
     ],
 );
 
-my $result = $dispatcher->run("foo");
-is($result, "foo matched");
+my $result = $dispatcher->run("foobar");
+is($result, "foobar matched");
 
 $dispatcher->add_rule(
-    Path::Dispatcher::Rule::Tokens->new(
-        tokens => ['foo', 'bar'],
-        block  => sub { "foobar matched" },
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/^foo/,
+        block => sub { "foo matched" },
     ),
 );
 
-$result = $dispatcher->run("foo bar");
+$result = $dispatcher->run("foobar");
 is($result, "foobar matched");
 
-$result = $dispatcher->run("foo");
-is($result, "foo matched");
+my $dispatch = $dispatcher->dispatch("foobar");
+for my $match ($dispatch->matches) {
+    $match->ends_dispatch(0);
+}
+
+$result = $dispatch->run("foobar");
+is($result, "foobar matched");
 
-my @results = $dispatcher->run("foo");
-is_deeply(\@results, ["foo matched", "foobar matched"]);
+my @results = $dispatch->run("foobar");
+is_deeply(\@results, ["foobar matched", "foo matched"]);
 

commit b246e86365669ac41f9d94f508a85e6bfe755a52
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:36:25 2008 +0000

    Changes thus far

diff --git a/Changes b/Changes
index eba743a..ab36d08 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,14 @@
 Revision history for Path-Dispatcher
 
 0.05
+        The Dispatch's run method will now collect return values and return
+            them
+
+        Match:
+            ends_dispatch is now an attribute
+
+        Dispatcher:
+            Allow rules to be specified in the constructor
 
 0.04 Tue Oct 28 17:56:41 2008
         Dist fixes

commit 010e81762bd0c15a2cde80ae56de9463ac7bb771
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:43:21 2008 +0000

    Add support for case insensitive matching in tokens rules

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 6eb3b7c..98d36a9 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -41,6 +41,12 @@ has delimiter => (
     default => ' ',
 );
 
+has case_sensitive => (
+    is      => 'rw',
+    isa     => 'Bool',
+    default => 1,
+);
+
 sub _match {
     my $self = shift;
     my $path = shift;
@@ -72,6 +78,7 @@ sub _match_token {
         }
     }
     elsif ($Str->check($expected)) {
+        ($got, $expected) = (lc $got, lc $expected) if !$self->case_sensitive;
         return $got eq $expected;
     }
     elsif ($RegexpRef->check($expected)) {
@@ -147,5 +154,10 @@ path. In the future this may be extended to support having a regex delimiter.
 The default is a space, but if you're matching URLs you probably want to change
 this to a slash.
 
+=head2 case_sensitive
+
+Decide whether the rule matching is case sensitive. Default is 1, case
+sensitive matching.
+
 =cut
 

commit a9b7ab31d7261032c8f4c99223fd42c3be7d0a28
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 01:47:24 2008 +0000

    Plenty of tokens tests

diff --git a/t/013-tokens.t b/t/013-tokens.t
index 61516f6..85baa72 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 9;
+use Test::More tests => 10;
 use Path::Dispatcher;
 
 my @calls;
@@ -62,3 +62,13 @@ is_deeply([splice @calls], [ ['Deep', 'Man', undef] ], "alternations can be arbi
 $dispatcher->run('Not Appearing in this Dispatcher Man');
 is_deeply([splice @calls], [ ], "no match");
 
+my $rule = Path::Dispatcher::Rule::Tokens->new(
+    tokens         => ['path', 'dispatcher'],
+    delimiter      => '::',
+    prefix         => 1,
+    case_sensitive => 0,
+);
+
+my $match = $rule->match('Path::Dispatcher::Rule::Tokens');
+is($match->leftover, 'Rule::Tokens');
+

commit 835cc09e015f94fd2cb745a28aea258396e5879f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 04:29:46 2008 +0000

    Allow a declarative dispatcher to decide whether it wants its token-rules to be case sensitive

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index dc96792..557296d 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -18,6 +18,7 @@ my $exporter = Sub::Exporter::build_exporter({
 });
 
 sub token_delimiter { ' ' }
+sub case_sensitive_tokens { undef }
 
 sub import {
     my $self = shift;
@@ -113,9 +114,12 @@ sub build_sugar {
 my %rule_creator = (
     ARRAY => sub {
         my ($self, $tokens, $block) = @_;
+        my $case_sensitive = $self->case_sensitive_tokens;
+
         Path::Dispatcher::Rule::Tokens->new(
             tokens => $tokens,
             delimiter => $self->token_delimiter,
+            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
             $block ? (block => $block) : (),
         ),
     },

commit c700252cfac4c60a53af0b91b2fe8a6b1107e923
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Oct 30 04:30:21 2008 +0000

    Tests for token_delimiter and case_sensitive_tokens config

diff --git a/t/104-config.t b/t/104-config.t
new file mode 100644
index 0000000..d0d28f4
--- /dev/null
+++ b/t/104-config.t
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+my @calls;
+
+do {
+    package RESTy::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    sub token_delimiter { '/' }
+    sub case_sensitive_tokens { 0 }
+
+    on ['=', 'model', 'Comment'] => sub { push @calls, $3 };
+};
+
+ok(RESTy::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative sets up ISA");
+
+RESTy::Dispatcher->run('= model Comment');
+is_deeply([splice @calls], []);
+
+RESTy::Dispatcher->run('/=/model/Comment');
+is_deeply([splice @calls], ["Comment"]);
+
+RESTy::Dispatcher->run('/=/model/comment');
+is_deeply([splice @calls], ["comment"]);
+
+RESTy::Dispatcher->run('///=///model///COMMENT///');
+is_deeply([splice @calls], ["COMMENT"]);
+

commit f282fd148b78ab0038b21188f7d9654704804f09
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:00 2008 +0000

    Include the regex in the (unlikely) error just in case

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 98eb23a..c6fa610 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -72,7 +72,7 @@ sub run_with_number_vars {
     # we need to do the match anyway, because we have to clear the number vars
     ($str, $re) = ("x", "x") if length($str) == 0;
     $str =~ $re
-        or die "Unable to match '$str' against a copy of itself!";
+        or die "Unable to match '$str' against a copy of itself ($re)!";
 
     $code->();
 }

commit 99f23a1f41b923876ef47bd3000adf3f9ada6c6c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:21 2008 +0000

    Fill in the blanks of the PDRD synopsis :)

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index d5a0fd7..d22070e 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -34,12 +34,12 @@ Path::Dispatcher::Rule::Dispatch - redispatch
     my $dispatcher = Path::Dispatcher->new(
         rules => [
             Path::Dispatcher::Rule::Tokens->new(
-                tokens => [  ],
-                block  => sub {  },
+                tokens => [ 'help' ],
+                block  => sub { show_help },
             ),
             Path::Dispatcher::Rule::Tokens->new(
-                tokens => [  ],
-                block  => sub {  },
+                tokens => [ 'quit' ],
+                block  => sub { exit },
             ),
         ],
     );
@@ -48,7 +48,7 @@ Path::Dispatcher::Rule::Dispatch - redispatch
         dispatcher => $dispatcher,
     );
 
-    $rule->run("");
+    $rule->run("help");
 
 =head1 DESCRIPTION
 

commit 721e71c1a768ea5a3f417da0aff96440206f34e0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:23 2008 +0000

    Test that "use Path::Dispatcher -base" throws a helpful error

diff --git a/t/900-use-path-dispatcher.t b/t/900-use-path-dispatcher.t
new file mode 100644
index 0000000..2061345
--- /dev/null
+++ b/t/900-use-path-dispatcher.t
@@ -0,0 +1,12 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+eval "
+    package MyApp::Dispatcher;
+    use Path::Dispatcher -base;
+";
+
+like($@, qr/^use Path::Dispatcher \(-base\) called by MyApp::Dispatcher\. Did you mean Path::Dispatcher::Declarative\?/);
+

commit c75d954d1e348142653927e8c5f266b185c973d0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:27 2008 +0000

    Ignore cover_db

commit 9b815f10509c738543b8150165fde5c92e0e838e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:41 2008 +0000

    Add an alias "has_match"

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 1f53f6a..1ce4e2b 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -13,12 +13,13 @@ has _matches => (
     provides  => {
         push     => 'add_match',
         elements => 'matches',
-        count    => 'has_matches',
+        count    => 'has_match',
     },
 );
 
-# alias add_matches -> add_match
+# aliases
 __PACKAGE__->meta->add_method(add_matches => __PACKAGE__->can('add_match'));
+__PACKAGE__->meta->add_method(has_matches => __PACKAGE__->can('has_match'));
 
 sub run {
     my $self = shift;

commit 907c207a348f5303455850281bb3f01afa305405
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:47 2008 +0000

    Test that the matcher returning the wrong thing throws an error. This will probably never come up in practice but it is a limitation for now

diff --git a/t/901-return-values.t b/t/901-return-values.t
new file mode 100644
index 0000000..5822f7b
--- /dev/null
+++ b/t/901-return-values.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Test::Exception;
+use Path::Dispatcher;
+
+my $dispatcher = Path::Dispatcher->new(
+    rules => [
+        Path::Dispatcher::Rule::CodeRef->new(
+            matcher => sub { [{ cant_handle_complex_list_of_results => 1 }] },
+        ),
+    ],
+);
+
+throws_ok {
+    $dispatcher->dispatch('foo');
+} qr/Invalid result 'HASH\(\w+\)', results must be plain strings/;
+

commit 92853807f25e0db7e138bcb886db748b1ed3e8f0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:55 2008 +0000

    We require Test::Exception now

diff --git a/Makefile.PL b/Makefile.PL
index b628a97..0d0ef67 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -8,6 +8,7 @@ requires 'MooseX::AttributeHelpers';
 requires 'Sub::Exporter';
 
 build_requires 'Test::More';
+build_requires 'Test::Exception';
 
 WriteAll;
 

commit c2d7cc7b43012244c1a2ff8759a44fa1ca90f0c8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:35:58 2008 +0000

    first_match

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 1ce4e2b..0fb8cf8 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -14,6 +14,7 @@ has _matches => (
         push     => 'add_match',
         elements => 'matches',
         count    => 'has_match',
+        first    => 'first_match',
     },
 );
 

commit 930ad4ce35eb755cccf92f108d795a26efe45b73
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:02 2008 +0000

    Test that trying to run a rule that lacks a coderef is an error

diff --git a/t/902-coderef.t b/t/902-coderef.t
new file mode 100644
index 0000000..d58b208
--- /dev/null
+++ b/t/902-coderef.t
@@ -0,0 +1,22 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 3;
+use Test::Exception;
+use Path::Dispatcher::Rule::Tokens;
+
+my $rule = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['bus', 'train'],
+);
+
+throws_ok {
+    $rule->run;
+} qr/^No codeblock to run/;
+
+my $match = $rule->match('bus train');
+ok($match, "matched the tokens");
+
+throws_ok {
+    $match->run;
+} qr/^No codeblock to run/;
+

commit 6b9026f3f551cee113151bed8fa2e3e762689607
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:07 2008 +0000

    No need to throw an explicit error here, Perl gives an even better method-missing error

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 00c1c2e..ecac35d 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -18,8 +18,6 @@ has prefix => (
     default => 0,
 );
 
-sub _match { die "_match not implemented in " . (blessed($_[0]) || $_[0]) }
-
 sub match {
     my $self = shift;
     my $path = shift;

commit b49e5d68f4f1edafd5b23e1818c810898592f5d9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:24 2008 +0000

    Argh, fix the type constraint check for tokens

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 98d36a9..04a6093 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -30,7 +30,6 @@ subtype 'Path::Dispatcher::Tokens'
 has tokens => (
     is         => 'rw',
     isa        => 'Path::Dispatcher::Tokens',
-    isa        => 'ArrayRef',
     auto_deref => 1,
     required   => 1,
 );
diff --git a/t/903-weird-token.t b/t/903-weird-token.t
new file mode 100644
index 0000000..7a017b1
--- /dev/null
+++ b/t/903-weird-token.t
@@ -0,0 +1,13 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher;
+use Test::Exception;
+
+throws_ok {
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => [ 'foo', { bar => 1 }, 'baz' ],
+    )
+} qr/^Attribute \(tokens\) does not pass the type constraint because: Validation failed for 'Path::Dispatcher::Tokens' failed with value ARRAY\(\w+\)/;
+

commit f01874e5562f1d0584109b7eb85a394bb4e2a707
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:32 2008 +0000

    Test coverage improvements

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 04a6093..322756b 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -83,8 +83,9 @@ sub _match_token {
     elsif ($RegexpRef->check($expected)) {
         return $got =~ $expected;
     }
-
-    return 0;
+    else {
+        die "Unexpected token '$expected'"; # the irony is not lost on me :)
+    }
 }
 
 sub tokenize {
diff --git a/t/013-tokens.t b/t/013-tokens.t
index 85baa72..18cd2fc 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 10;
+use Test::More tests => 11;
 use Path::Dispatcher;
 
 my @calls;
@@ -17,6 +17,8 @@ $dispatcher->add_rule(
 $dispatcher->run('foo bar');
 is_deeply([splice @calls], [ ['foo', 'bar', undef] ], "correctly populated number vars from [str, str] token rule");
 
+ok(!$dispatcher->dispatch('foo bar baz')->has_match, "no match for 'foo bar baz' because the rule isn't a prefix");
+
 $dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => ['foo', qr/bar/],
diff --git a/t/903-weird-token.t b/t/903-weird-token.t
index 7a017b1..0076f70 100644
--- a/t/903-weird-token.t
+++ b/t/903-weird-token.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 2;
 use Path::Dispatcher;
 use Test::Exception;
 
@@ -11,3 +11,12 @@ throws_ok {
     )
 } qr/^Attribute \(tokens\) does not pass the type constraint because: Validation failed for 'Path::Dispatcher::Tokens' failed with value ARRAY\(\w+\)/;
 
+my $rule = Path::Dispatcher::Rule::Tokens->new(
+    tokens => [],
+);
+
+push @{ $rule->{tokens} }, { weird_token => 1 };
+
+throws_ok {
+    $rule->match("mezzanine");
+} qr/^Unexpected token 'HASH\(\w+\)'/;

commit 85ba810fa02529c919d99a4f62708db47cab97fa
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:35 2008 +0000

    Make the Tokens type constraint do what I actually want it to; TODO: make it sane

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 322756b..595316a 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -13,19 +13,21 @@ extends 'Path::Dispatcher::Rule';
 #     - strings
 #     - regular expressions
 
-my $Str       = find_type_constraint('Str');
-my $RegexpRef = find_type_constraint('RegexpRef');
-my $ArrayRef  = find_type_constraint('ArrayRef');
-
-subtype 'Path::Dispatcher::Token'
-     => as 'Defined'
-     => where { $Str->check($_) || $RegexpRef->check($_) };
-
-subtype 'Path::Dispatcher::TokenAlternation'
-     => as 'ArrayRef[Path::Dispatcher::Token]';
+my $Str      = find_type_constraint('Str');
+my $Regex    = find_type_constraint('RegexpRef');
+my $ArrayRef = find_type_constraint('ArrayRef');
+
+my $Alternation;
+$Alternation = subtype as 'Defined'
+     => where {
+         return $Str->check($_) || $Regex->check($_) if ref($_) ne 'ARRAY';
+         $Alternation->check($_) or return for @$_;
+         1
+     };
 
 subtype 'Path::Dispatcher::Tokens'
-     => as 'ArrayRef[Path::Dispatcher::Token|Path::Dispatcher::TokenAlternation]';
+     => as 'ArrayRef'
+     => where { $Alternation->check($_) or return for @$_; 1 };
 
 has tokens => (
     is         => 'rw',
@@ -80,7 +82,7 @@ sub _match_token {
         ($got, $expected) = (lc $got, lc $expected) if !$self->case_sensitive;
         return $got eq $expected;
     }
-    elsif ($RegexpRef->check($expected)) {
+    elsif ($Regex->check($expected)) {
         return $got =~ $expected;
     }
     else {

commit 53c39092ea1fa8bf020a611ae854385543b7f493
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:36:39 2008 +0000

    Remove the "if length(leftover) && !prefix" check; it doesn't actually make sense

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index ecac35d..a47a512 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -27,10 +27,6 @@ sub match {
 
     $leftover = '' if !defined($leftover);
 
-    # if we're not matching only a prefix then require the leftover to be empty
-    return if length($leftover)
-           && !$self->prefix;
-
     # make sure that the returned values are PLAIN STRINGS
     # later we will stick them into a regular expression to populate $1 etc
     # which will blow up later!
diff --git a/t/013-tokens.t b/t/013-tokens.t
index 18cd2fc..33609e8 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -17,8 +17,6 @@ $dispatcher->add_rule(
 $dispatcher->run('foo bar');
 is_deeply([splice @calls], [ ['foo', 'bar', undef] ], "correctly populated number vars from [str, str] token rule");
 
-ok(!$dispatcher->dispatch('foo bar baz')->has_match, "no match for 'foo bar baz' because the rule isn't a prefix");
-
 $dispatcher->add_rule(
     Path::Dispatcher::Rule::Tokens->new(
         tokens => ['foo', qr/bar/],

commit 15658550341466462579dd574d1a10df7d6cbafd
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:37:01 2008 +0000

    Ensure that token matching is by default case sensitive in the declarative dispatcher

diff --git a/t/100-declarative.t b/t/100-declarative.t
index accfb99..dc059a2 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 5;
+use Test::More tests => 7;
 
 my @calls;
 
@@ -13,6 +13,10 @@ do {
         push @calls, [$1, $2, $3];
     };
 
+    on ['token', 'matching'] => sub {
+        push @calls, [$1, $2];
+    };
+
     rewrite quux => 'bar';
     rewrite qr/^quux-(.*)/ => sub { "bar:$1" };
 };
@@ -35,3 +39,11 @@ is_deeply([splice @calls], [
     [ 'b', 'ar', ':hello' ],
 ]);
 
+MyApp::Dispatcher->run('token matching');
+is_deeply([splice @calls], [
+    [ 'token', 'matching' ],
+]);
+
+MyApp::Dispatcher->run('Token Matching');
+is_deeply([splice @calls], [], "token matching is by default case sensitive");
+

commit 40da8c848d79a61bb84065de704b5e419914e8aa
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Nov 4 22:37:23 2008 +0000

    Doc, ignore Makefile.old

diff --git a/Changes b/Changes
index ab36d08..cadfba7 100644
--- a/Changes
+++ b/Changes
@@ -1,14 +1,22 @@
 Revision history for Path-Dispatcher
 
 0.05
-        The Dispatch's run method will now collect return values and return
-            them
+        Improve test coverage
 
         Match:
             ends_dispatch is now an attribute
 
+        Rule::Tokens:
+            Support for case insensitive matching
+            Fix for tokens' type constraint
+
         Dispatcher:
-            Allow rules to be specified in the constructor
+            Allow rules to be specified in the constructor (a typo prevented it)
+
+        Dispatch:
+            first_match, has_match methods which do what you'd expect
+            The run method will now collect return values and return them
+
 
 0.04 Tue Oct 28 17:56:41 2008
         Dist fixes
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 0fb8cf8..956eaaa 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -97,5 +97,9 @@ matched.
 
 Executes matches until a match's C<ends_dispatch> returns true.
 
+Each match's L<Path::Dispatcher::Match/run> method is evaluated in scalar
+context. The return value of this method is a list of these scalars (or the
+first if called in scalar context).
+
 =cut
 

commit 17d8da1a05778b08efadff4755c68958b7747e99
Author: clkao <clkao at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 03:40:36 2008 +0000

    fix test plan.

diff --git a/t/013-tokens.t b/t/013-tokens.t
index 33609e8..85baa72 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 11;
+use Test::More tests => 10;
 use Path::Dispatcher;
 
 my @calls;

commit d017c835cf6e569a12ce4be868d1759f3af27e4f
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:36:59 2008 +0000

    Add redispatch_to sugar

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 557296d..601edf0 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -106,6 +106,20 @@ sub build_sugar {
 
             $into->_add_rule($under, @_);
         },
+        redispatch_to => sub {
+            my ($dispatcher) = @_;
+
+            # assume it's a declarative dispatcher
+            if (!ref($dispatcher)) {
+                $dispatcher = $dispatcher->dispatcher;
+            }
+
+            my $redispatch = Path::Dispatcher::Rule::Dispatch->new(
+                dispatcher => $dispatcher,
+            );
+
+            $into->_add_rule($redispatch);
+        },
         next_rule => sub { die "Path::Dispatcher next rule\n" },
         last_rule => sub { die "Path::Dispatcher abort\n" },
     };

commit ace2769398359814faeb82b61ad723e0cf0d8e67
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:44:46 2008 +0000

    Begin improving the declarative tests

diff --git a/t/101-subclass.t b/t/101-subclass.t
index e35ad24..1af982f 100644
--- a/t/101-subclass.t
+++ b/t/101-subclass.t
@@ -1,45 +1,42 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 5;
-use lib 't/lib';
-use Path::Dispatcher::Test::App;
+use Test::More tests => 2;
 
-our @calls;
+my @calls;
 
-Path::Dispatcher::Test::Framework->run('foo');
-is_deeply([splice @calls], [
-    'framework before foo',
-#    'framework on foo',
-#    'framework after foo',
-]);
+do {
+    package MyFramework::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
 
-Path::Dispatcher::Test::App->run('foo');
-is_deeply([splice @calls], [
-    'app before foo',
-#    'app after foo',
-#    'framework before foo',
-#    'framework on foo',
-#    'framework after foo',
-]);
+    on 'quit' => sub { push @calls, 'framework: quit' };
+
+    package MyApp::Dispatcher;
+    # this hack is here because "use" expects there to be a file for the module
+    BEGIN { MyFramework::Dispatcher->import("-base") }
 
-Path::Dispatcher::Test::App->dispatcher->add_rule(
-    Path::Dispatcher::Rule::Regex->new(
-        regex => qr/foo/,
-        block => sub {
-            push @calls, 'app on foo';
-        },
-    ),
-);
+    on qr/.*/ => sub {
+        push @calls, 'app: first .*';
+        next_rule;
+    };
 
-Path::Dispatcher::Test::App->run('foo');
+    redispatch_to('MyFramework::Dispatcher');
+
+    on qr/.*/ => sub {
+        push @calls, 'app: second .*';
+        next_rule;
+    };
+};
+
+MyApp::Dispatcher->run("quit");
 is_deeply([splice @calls], [
-    'app before foo',
-#    'app on foo',
-#    'app after foo',
+    'app: first .*',
+    'framework: quit',
 ]);
 
-for ('Path::Dispatcher::Test::Framework', 'Path::Dispatcher::Test::App') {
-    is($_->dispatcher->name, $_, "correct dispatcher name for $_");
-}
+MyApp::Dispatcher->run("other");
+is_deeply([splice @calls], [
+    'app: first .*',
+    'app: second .*',
+]);
 

commit 04f111d97be5c2a944ca6eb9a86f77fa0fa00420
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:45:11 2008 +0000

    declarative abort test fixes

diff --git a/t/102-abort.t b/t/102-abort.t
index ea0decb..2c2ae15 100644
--- a/t/102-abort.t
+++ b/t/102-abort.t
@@ -2,17 +2,59 @@
 use strict;
 use warnings;
 use Test::More tests => 2;
-use lib 't/lib';
-use Path::Dispatcher::Test::App;
 
-our @calls;
+my @calls;
 
-Path::Dispatcher::Test::App->run('abort');
+do {
+    package MyFramework::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+    on qr/abort/ => sub {
+        push @calls, 'framework on abort';
+    };
+
+    on qr/next rule/ => sub {
+        push @calls, 'framework before next_rule';
+        next_rule;
+        push @calls, 'framework after next_rule';
+    };
+
+    on qr/next rule/ => sub {
+        push @calls, 'framework before next_rule 2';
+        next_rule;
+        push @calls, 'framework after next_rule 2';
+    };
+
+    package MyApp::Dispatcher;
+    # this hack is here because "use" expects there to be a file for the module
+    BEGIN { MyFramework::Dispatcher->import("-base") }
+
+    before qr/abort/ => sub {
+        push @calls, 'app before abort';
+        last_rule;
+        push @calls, 'app after abort';
+    };
+
+    on qr/next rule/ => sub {
+        push @calls, 'app before next_rule';
+        next_rule;
+        push @calls, 'app after next_rule';
+    };
+
+    on qr/next rule/ => sub {
+        push @calls, 'app before next_rule 2';
+        next_rule;
+        push @calls, 'app after next_rule 2';
+    };
+
+    redispatch_to('MyFramework::Dispatcher');
+};
+
+MyApp::Dispatcher->run('abort');
 is_deeply([splice @calls], [
     'app before abort',
 ]);
 
-Path::Dispatcher::Test::App->run('next rule');
+MyApp::Dispatcher->run('next rule');
 is_deeply([splice @calls], [
     'app before next_rule',
     'app before next_rule 2',

commit 6ee8cc85fff43190920ccf9a5e58f22f51cb4e16
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:45:23 2008 +0000

    t/103-input test fixes

diff --git a/t/103-input.t b/t/103-input.t
index fc66548..c01675a 100644
--- a/t/103-input.t
+++ b/t/103-input.t
@@ -2,12 +2,42 @@
 use strict;
 use warnings;
 use Test::More tests => 1;
-use lib 't/lib';
-use Path::Dispatcher::Test::App;
 
-our @calls;
+my @calls;
 
-Path::Dispatcher::Test::App->run('args', 1..3);
+do {
+    package MyFramework::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    on qr/a(rg)s/ => sub {
+        push @calls, {
+            from => "framework",
+            args => [@_],
+            it   => $_,
+            one  => $1,
+            two  => $2,
+        };
+    };
+
+    package MyApp::Dispatcher;
+    # this hack is here because "use" expects there to be a file for the module
+    BEGIN { MyFramework::Dispatcher->import("-base") }
+
+    on qr/ar(g)s/ => sub {
+        push @calls, {
+            from => "app",
+            args => [@_],
+            it   => $_,
+            one  => $1,
+            two  => $2,
+        };
+        next_rule;
+    };
+
+    redispatch_to('MyFramework::Dispatcher');
+};
+
+MyApp::Dispatcher->run('args', 1..3);
 is_deeply([splice @calls], [
     {
         from => 'app',
@@ -18,7 +48,7 @@ is_deeply([splice @calls], [
     },
     {
         from => 'framework',
-        one  => 'g',
+        one  => 'rg',
         two  => undef,
         it   => 'args',
         args => [1, 2, 3],

commit e1b081fa481b531fd067eb4cba002e22aab4deba
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:45:42 2008 +0000

    Remove t/lib

diff --git a/t/lib/Path/Dispatcher/Test/App.pm b/t/lib/Path/Dispatcher/Test/App.pm
deleted file mode 100644
index 28aa5a7..0000000
--- a/t/lib/Path/Dispatcher/Test/App.pm
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env perl
-package Path::Dispatcher::Test::App;
-use strict;
-use warnings;
-use Path::Dispatcher::Test::Framework -base;
-
-before qr/foo/ => sub {
-    push @main::calls, 'app before foo';
-};
-
-after qr/foo/ => sub {
-    push @main::calls, 'app after foo';
-};
-
-before qr/abort/ => sub {
-    push @main::calls, 'app before abort';
-    last_rule;
-    push @main::calls, 'app after abort';
-};
-
-on qr/next rule/ => sub {
-    push @main::calls, 'app before next_rule';
-    next_rule;
-    push @main::calls, 'app after next_rule';
-};
-
-on qr/next rule/ => sub {
-    push @main::calls, 'app before next_rule 2';
-    next_rule;
-    push @main::calls, 'app after next_rule 2';
-};
-
-on qr/ar(g)s/ => sub {
-    push @main::calls, {
-        from => "app",
-        args => [@_],
-        it   => $_,
-        one  => $1,
-        two  => $2,
-    };
-    next_rule;
-};
-
-__PACKAGE__->dispatcher->add_rule(
-    Path::Dispatcher::Rule::Dispatch->new(
-        dispatcher => Path::Dispatcher::Test::Framework->dispatcher,
-    )
-);
-
-1;
-
diff --git a/t/lib/Path/Dispatcher/Test/Framework.pm b/t/lib/Path/Dispatcher/Test/Framework.pm
deleted file mode 100644
index c222c14..0000000
--- a/t/lib/Path/Dispatcher/Test/Framework.pm
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env perl
-package Path::Dispatcher::Test::Framework;
-use strict;
-use warnings;
-use Path::Dispatcher::Declarative -base;
-
-before qr/foo/ => sub {
-    push @main::calls, 'framework before foo';
-};
-
-on qr/foo/ => sub {
-    push @main::calls, 'framework on foo';
-};
-
-after qr/foo/ => sub {
-    push @main::calls, 'framework after foo';
-};
-
-on qr/abort/ => sub {
-    push @main::calls, 'framework on abort';
-};
-
-on qr/next rule/ => sub {
-    push @main::calls, 'framework before next_rule';
-    next_rule;
-    push @main::calls, 'framework after next_rule';
-};
-
-on qr/next rule/ => sub {
-    push @main::calls, 'framework before next_rule 2';
-    next_rule;
-    push @main::calls, 'framework after next_rule 2';
-};
-
-on qr/ar(g)s/ => sub {
-    push @main::calls, {
-        from => "framework",
-        args => [@_],
-        it   => $_,
-        one  => $1,
-        two  => $2,
-    };
-};
-
-1;
-

commit 261b57e2da9f8e8d0e361aa0ef2b91269aec6e71
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:46:04 2008 +0000

    Make sure that redispatching directly to a dispatcher object works too

diff --git a/t/103-input.t b/t/103-input.t
index c01675a..16329c9 100644
--- a/t/103-input.t
+++ b/t/103-input.t
@@ -34,7 +34,7 @@ do {
         next_rule;
     };
 
-    redispatch_to('MyFramework::Dispatcher');
+    redispatch_to(MyFramework::Dispatcher->dispatcher);
 };
 
 MyApp::Dispatcher->run('args', 1..3);

commit 846c9fd3d784dd38a5544fd8a9377bd7cc1450bf
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:46:07 2008 +0000

    Plan fix

commit 83e752be59808b6932bdb3a26ab38874b332517e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 9 17:53:47 2008 +0000

    Mention in the top-level doc that run passes on args to each codeblock

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 4c9ae2e..4622c03 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -157,6 +157,9 @@ Dispatches on the path and then invokes the C<run> method on the
 L<Path::Dispatcher::Dispatch> object, for when you don't need to inspect the
 dispatch.
 
+The args are passed down directly into each rule codeblock. No other args are
+given to the codeblock.
+
 =head1 AUTHOR
 
 Shawn M Moore, C<< <sartak at bestpractical.com> >>

commit 136d947008cc798329bf19265fcdfe5559b272b4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Fri Nov 14 18:04:15 2008 +0000

    Add an Always rule

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index a47a512..8457a1b 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -60,11 +60,12 @@ __PACKAGE__->meta->make_immutable;
 no Moose;
 
 # don't require others to load our subclasses explicitly
+require Path::Dispatcher::Rule::Always;
 require Path::Dispatcher::Rule::CodeRef;
+require Path::Dispatcher::Rule::Dispatch;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
 require Path::Dispatcher::Rule::Under;
-require Path::Dispatcher::Rule::Dispatch;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Always.pm b/lib/Path/Dispatcher/Rule/Always.pm
new file mode 100644
index 0000000..81f9cd3
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Always.pm
@@ -0,0 +1,30 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Always;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+    return (1, $path);
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Always - always matches
+
+=head1 DESCRIPTION
+
+Rules of this class always match. If a prefix match is requested, the full path
+is returned as leftover.
+
+=cut
+
+

commit c100760d08326e4dbadc93fd17099a6e0bee2b95
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Fri Nov 14 18:04:30 2008 +0000

    Special case on '' => sub {} to match everything

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 601edf0..0b10a05 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -125,7 +125,7 @@ sub build_sugar {
     };
 }
 
-my %rule_creator = (
+my %rule_creators = (
     ARRAY => sub {
         my ($self, $tokens, $block) = @_;
         my $case_sensitive = $self->case_sensitive_tokens;
@@ -159,13 +159,23 @@ my %rule_creator = (
             $block ? (block => $block) : (),
         ),
     },
+    empty => sub {
+        my ($self, $undef, $block) = @_;
+        Path::Dispatcher::Rule::Always->new(
+            $block ? (block => $block) : (),
+        ),
+    },
 );
 
 sub _create_rule {
     my ($self, $stage, $matcher, $block) = @_;
 
-    my $rule_creator = $rule_creator{ ref $matcher }
-        or die "I don't know how to create a rule for type $matcher";
+    my $rule_creator;
+    $rule_creator   = $rule_creators{empty} if $matcher eq '';
+    $rule_creator ||= $rule_creators{ ref $matcher };
+
+    $rule_creator or die "I don't know how to create a rule for type $matcher";
+
     return $rule_creator->($self, $matcher, $block);
 }
 
diff --git a/t/105-always.t b/t/105-always.t
new file mode 100644
index 0000000..472b213
--- /dev/null
+++ b/t/105-always.t
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+my @calls;
+
+do {
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    on '' => sub {
+        push @calls, "empty: $_";
+    };
+};
+
+MyApp::Dispatcher->run("foo");
+is_deeply([splice @calls], ["empty: foo"]);
+

commit 62099daa0110b4115be8fc38432a5bb36f445ded
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sat Nov 15 21:38:50 2008 +0000

    Update Changes

diff --git a/Changes b/Changes
index cadfba7..94e7060 100644
--- a/Changes
+++ b/Changes
@@ -1,7 +1,8 @@
 Revision history for Path-Dispatcher
 
-0.05
+0.05 Sat Nov 15 16:36:41 2008
         Improve test coverage
+        New rule type "Always" which always matches.
 
         Match:
             ends_dispatch is now an attribute
@@ -17,6 +18,10 @@ Revision history for Path-Dispatcher
             first_match, has_match methods which do what you'd expect
             The run method will now collect return values and return them
 
+        Declarative:
+            redispatch_to sugar which adds a Dispatch rule
+            "on '' => sub" as a special case will match all paths
+
 
 0.04 Tue Oct 28 17:56:41 2008
         Dist fixes

commit a70e3982cd378f8c55b5fa70838507f892f3fca9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sat Nov 15 21:38:59 2008 +0000

    Bump to 0.06

diff --git a/Changes b/Changes
index 94e7060..1c7af7e 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.06
+
 0.05 Sat Nov 15 16:36:41 2008
         Improve test coverage
         New rule type "Always" which always matches.
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 4622c03..0b3dc2d 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,7 +3,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.05';
+our $VERSION = '0.06';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit b1d8126c487ca2e2aa4ebffa9a9b3610a19bdaf6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 16 01:59:01 2008 +0000

    Add an "empty" rule

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 8457a1b..19a034d 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -63,6 +63,7 @@ no Moose;
 require Path::Dispatcher::Rule::Always;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Dispatch;
+require Path::Dispatcher::Rule::Empty;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
 require Path::Dispatcher::Rule::Under;
diff --git a/lib/Path/Dispatcher/Rule/Empty.pm b/lib/Path/Dispatcher/Rule/Empty.pm
new file mode 100644
index 0000000..bce7092
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Empty.pm
@@ -0,0 +1,29 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Always;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+    return 0 if length $path;
+    return (1, $path);
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Empty - matches only the empty path
+
+=head1 DESCRIPTION
+
+Rules of this class match only the empty path.
+
+=cut
+

commit 442037491962a4d313dfd85754490070c742bc88
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 16 02:00:21 2008 +0000

    Class name fix

diff --git a/lib/Path/Dispatcher/Rule/Empty.pm b/lib/Path/Dispatcher/Rule/Empty.pm
index bce7092..9b2a56f 100644
--- a/lib/Path/Dispatcher/Rule/Empty.pm
+++ b/lib/Path/Dispatcher/Rule/Empty.pm
@@ -1,5 +1,5 @@
 #!/usr/bin/env perl
-package Path::Dispatcher::Rule::Always;
+package Path::Dispatcher::Rule::Empty;
 use Moose;
 extends 'Path::Dispatcher::Rule';
 

commit 653ba704ebe4a44dcbb881ffe42503ef04b748d5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 16 02:01:34 2008 +0000

    Make "on ''" match only the empty path

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 0b10a05..7bfce0a 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -161,7 +161,7 @@ my %rule_creators = (
     },
     empty => sub {
         my ($self, $undef, $block) = @_;
-        Path::Dispatcher::Rule::Always->new(
+        Path::Dispatcher::Rule::Empty->new(
             $block ? (block => $block) : (),
         ),
     },
diff --git a/t/105-always.t b/t/105-always.t
index 472b213..00b7840 100644
--- a/t/105-always.t
+++ b/t/105-always.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 my @calls;
 
@@ -15,5 +15,8 @@ do {
 };
 
 MyApp::Dispatcher->run("foo");
-is_deeply([splice @calls], ["empty: foo"]);
+is_deeply([splice @calls], []);
+
+MyApp::Dispatcher->run("");
+is_deeply([splice @calls], ["empty: "]);
 

commit 27ee9112702016e6951e44357650287e328529b7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 16 02:04:58 2008 +0000

    Change

diff --git a/Changes b/Changes
index 1c7af7e..b9f4682 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,10 @@
 Revision history for Path-Dispatcher
 
-0.06
+0.06 Sat Nov 15 21:02:29 2008
+        New rule type "Empty" which matches only the empty path.
+
+        Declarative:
+            on '' now matches only the empty path.
 
 0.05 Sat Nov 15 16:36:41 2008
         Improve test coverage

commit 272d7520055cd0967f5a81394fbb41197250e7e5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sun Nov 16 02:05:02 2008 +0000

    Bump to 0.07

diff --git a/Changes b/Changes
index b9f4682..4f272a6 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.07
+
 0.06 Sat Nov 15 21:02:29 2008
         New rule type "Empty" which matches only the empty path.
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 0b3dc2d..af5d460 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -3,7 +3,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.06';
+our $VERSION = '0.07';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit c4b3b2e9159468cf18de6d7758a1ba76aa4c37c0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:06:39 2008 +0000

    PD::Path class

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
new file mode 100644
index 0000000..667bb4d
--- /dev/null
+++ b/lib/Path/Dispatcher/Path.pm
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Path;
+use Moose;
+
+has path => (
+    is        => 'rw',
+    isa       => 'Str',
+    predicate => 'has_path',
+);
+
+has metadata => (
+    is        => 'rw',
+    isa       => 'HashRef',
+    predicate => 'has_metadata',
+);
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+

commit 0b41799707b1fb9f369f31c03c3150681a990505
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:06:57 2008 +0000

    First cut of conversion to use path objects

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index af5d460..d161a72 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -7,8 +7,10 @@ our $VERSION = '0.07';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;
+use Path::Dispatcher::Path;
 
-sub dispatch_class { 'Path::Dispatcher::Dispatch' }
+use constant dispatch_class => 'Path::Dispatcher::Dispatch';
+use constant path_class     => 'Path::Dispatcher::Path';
 
 has name => (
     is      => 'rw',
@@ -38,6 +40,12 @@ sub dispatch {
     my $self = shift;
     my $path = shift;
 
+    if (!ref($path)) {
+        $path = $self->path_class->new(
+            path => $path,
+        );
+    }
+
     my $dispatch = $self->dispatch_class->new;
 
     for my $rule ($self->rules) {
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index c6fa610..53bd313 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -2,11 +2,12 @@
 package Path::Dispatcher::Match;
 use Moose;
 
+use Path::Dispatcher::Path;
 use Path::Dispatcher::Rule;
 
 has path => (
     is       => 'rw',
-    isa      => 'Str',
+    isa      => 'Path::Dispatcher::Path',
     required => 1,
 );
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 19a034d..c860b64 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -4,7 +4,7 @@ use Moose;
 
 use Path::Dispatcher::Match;
 
-sub match_class { "Path::Dispatcher::Match" }
+use constant match_class => "Path::Dispatcher::Match";
 
 has block => (
     is        => 'rw',
diff --git a/lib/Path/Dispatcher/Rule/Always.pm b/lib/Path/Dispatcher/Rule/Always.pm
index 81f9cd3..f2a347f 100644
--- a/lib/Path/Dispatcher/Rule/Always.pm
+++ b/lib/Path/Dispatcher/Rule/Always.pm
@@ -6,7 +6,7 @@ extends 'Path::Dispatcher::Rule';
 sub _match {
     my $self = shift;
     my $path = shift;
-    return (1, $path);
+    return (1, $path->path);
 }
 
 __PACKAGE__->meta->make_immutable;
diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index e230cd8..bd8d6f3 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -11,9 +11,10 @@ has matcher => (
 
 sub _match {
     my $self = shift;
-    local $_ = shift; # path
+    my $path = shift;
 
-    return $self->matcher->($_);
+    local $_ = $path->path;
+    return $self->matcher->($path);
 }
 
 __PACKAGE__->meta->make_immutable;
@@ -54,7 +55,8 @@ necessary!
 A coderef that returns C<undef> if there's no match, otherwise a list of
 strings (the results).
 
-The coderef receives the path as both its one argument and C<$_>.
+The coderef receives the path object as its argument, and the path string as
+C<$_>.
 
 =cut
 
diff --git a/lib/Path/Dispatcher/Rule/Empty.pm b/lib/Path/Dispatcher/Rule/Empty.pm
index 9b2a56f..fe9cff1 100644
--- a/lib/Path/Dispatcher/Rule/Empty.pm
+++ b/lib/Path/Dispatcher/Rule/Empty.pm
@@ -6,8 +6,8 @@ extends 'Path::Dispatcher::Rule';
 sub _match {
     my $self = shift;
     my $path = shift;
-    return 0 if length $path;
-    return (1, $path);
+    return 0 if length $path->path;
+    return (1, $path->path);
 }
 
 __PACKAGE__->meta->make_immutable;
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index c1c9f39..0bb5d02 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -13,7 +13,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    return unless $path =~ $self->regex;
+    return unless $path->path =~ $self->regex;
 
     my @matches = map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#-;
 
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 595316a..8e98fe8 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -52,7 +52,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    my @tokens = $self->tokenize($path);
+    my @tokens = $self->tokenize($path->path);
     my @matched;
 
     for my $expected ($self->tokens) {
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 0ae8782..9e7e5dc 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -35,8 +35,9 @@ sub match {
         or return;
 
     my $suffix = $prefix_match->leftover;
+    my $new_path = $path->meta->clone_instance($path, path => $suffix);
 
-    return grep { defined } map { $_->match($suffix) } $self->rules;
+    return grep { defined } map { $_->match($new_path) } $self->rules;
 }
 
 __PACKAGE__->meta->make_immutable;

commit 799c39cdae013ec36ae899bfd06f4d44a07e5dac
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:13:44 2008 +0000

    Missed a spot

diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index 0bb5d02..f42c4a2 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -15,7 +15,7 @@ sub _match {
 
     return unless $path->path =~ $self->regex;
 
-    my @matches = map { substr($path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#-;
+    my @matches = map { substr($path->path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#-;
 
     # if $' is in the program at all, then it slows down every single regex
     # we only want to include it if we have to

commit bc6fc4daafe207420667e44003ce8b3f828b87bc
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:17:08 2008 +0000

    Support for single string arg in new: Path::Dispatcher::Path->new("/my/path")

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 667bb4d..b723ea4 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -14,6 +14,18 @@ has metadata => (
     predicate => 'has_metadata',
 );
 
+# allow Path::Dispatcher::Path->new($path)
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $self = shift;
+
+    if (@_ == 1 && !ref($_[0])) {
+        unshift @_, 'path';
+    }
+
+    $self->$orig(@_);
+};
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit 25f4e79eeb4e966b9101c77252ab8944b5a200d3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:21:16 2008 +0000

    In rule codeblocks, have $_ be the path string not the path object

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 53bd313..a0170d1 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -45,7 +45,7 @@ sub run {
     my $self = shift;
     my @args = @_;
 
-    local $_ = $self->path;
+    local $_ = $self->path->path;
 
     if ($self->set_number_vars) {
         return $self->run_with_number_vars(

commit 105f4a8b979c43d77d42354fd4d912d3021141ce
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:21:18 2008 +0000

    Test fixes

diff --git a/t/002-rule.t b/t/002-rule.t
index 931e444..3bc94e7 100644
--- a/t/002-rule.t
+++ b/t/002-rule.t
@@ -16,8 +16,8 @@ my $rule = Path::Dispatcher::Rule::Regex->new(
     },
 );
 
-isa_ok($rule->match('foobar'), 'Path::Dispatcher::Match');
-is_deeply($rule->match('foobar')->result, ['fo', 'ob']);
+isa_ok($rule->match(Path::Dispatcher::Path->new('foobar')), 'Path::Dispatcher::Match');
+is_deeply($rule->match(Path::Dispatcher::Path->new('foobar'))->result, ['fo', 'ob']);
 is_deeply([splice @calls], [], "block not called on match");
 
 $rule->run;
diff --git a/t/012-under.t b/t/012-under.t
index d8cd052..048b743 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -53,7 +53,7 @@ for my $path (keys %tests) {
     my $data = $tests{$path};
     my $name = $data->{name} || $path;
 
-    my $match = $under->match($path);
+    my $match = $under->match(Path::Dispatcher::Path->new($path));
     $match = !$match if $data->{fail};
     ok($match, $name);
 }
@@ -68,7 +68,7 @@ for my $path (keys %tests) {
     my $data = $tests{$path};
     my $name = $data->{name} || $path;
 
-    my $match = $under->match($path);
+    my $match = $under->match(Path::Dispatcher::Path->new($path));
     $match = !$match if $data->{fail} && !$data->{catchall};
     ok($match, $name);
 }
diff --git a/t/013-tokens.t b/t/013-tokens.t
index 85baa72..c3815c7 100644
--- a/t/013-tokens.t
+++ b/t/013-tokens.t
@@ -69,6 +69,6 @@ my $rule = Path::Dispatcher::Rule::Tokens->new(
     case_sensitive => 0,
 );
 
-my $match = $rule->match('Path::Dispatcher::Rule::Tokens');
+my $match = $rule->match(Path::Dispatcher::Path->new('Path::Dispatcher::Rule::Tokens'));
 is($match->leftover, 'Rule::Tokens');
 
diff --git a/t/014-tokens-prefix.t b/t/014-tokens-prefix.t
index f57e074..962a82c 100644
--- a/t/014-tokens-prefix.t
+++ b/t/014-tokens-prefix.t
@@ -12,10 +12,10 @@ my $rule = Path::Dispatcher::Rule::Tokens->new(
     prefix => 1,
 );
 
-ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
-ok($rule->match('foo bar'), "prefix matches the full path");
+ok(!$rule->match(Path::Dispatcher::Path->new('foo')), "prefix means the rule matches a prefix of the path, not the other way around");
+ok($rule->match(Path::Dispatcher::Path->new('foo bar')), "prefix matches the full path");
 
-my $match = $rule->match('foo bar baz');
+my $match = $rule->match(Path::Dispatcher::Path->new('foo bar baz'));
 ok($match, "prefix matches a prefix of the path");
 is_deeply($match->result, ["foo", "bar"]);
 is($match->leftover, "baz");
diff --git a/t/015-regex-prefix.t b/t/015-regex-prefix.t
index f7fa2c5..0ae9f94 100644
--- a/t/015-regex-prefix.t
+++ b/t/015-regex-prefix.t
@@ -12,10 +12,10 @@ my $rule = Path::Dispatcher::Rule::Regex->new(
     prefix => 1,
 );
 
-ok(!$rule->match('foo'), "prefix means the rule matches a prefix of the path, not the other way around");
-ok($rule->match('foo bar'), "prefix matches the full path");
-ok($rule->match('foo bar baz'), "prefix matches a prefix of the path");
-my $match = $rule->match('foobar:baz');
+ok(!$rule->match(Path::Dispatcher::Path->new('foo')), "prefix means the rule matches a prefix of the path, not the other way around");
+ok($rule->match(Path::Dispatcher::Path->new('foo bar')), "prefix matches the full path");
+ok($rule->match(Path::Dispatcher::Path->new('foo bar baz')), "prefix matches a prefix of the path");
+my $match = $rule->match(Path::Dispatcher::Path->new('foobar:baz'));
 
 ok($match, "matched foobar:baz");
 
diff --git a/t/902-coderef.t b/t/902-coderef.t
index d58b208..9496392 100644
--- a/t/902-coderef.t
+++ b/t/902-coderef.t
@@ -13,7 +13,7 @@ throws_ok {
     $rule->run;
 } qr/^No codeblock to run/;
 
-my $match = $rule->match('bus train');
+my $match = $rule->match(Path::Dispatcher::Path->new('bus train'));
 ok($match, "matched the tokens");
 
 throws_ok {
diff --git a/t/903-weird-token.t b/t/903-weird-token.t
index 0076f70..127ba72 100644
--- a/t/903-weird-token.t
+++ b/t/903-weird-token.t
@@ -18,5 +18,5 @@ my $rule = Path::Dispatcher::Rule::Tokens->new(
 push @{ $rule->{tokens} }, { weird_token => 1 };
 
 throws_ok {
-    $rule->match("mezzanine");
+    $rule->match(Path::Dispatcher::Path->new("mezzanine"));
 } qr/^Unexpected token 'HASH\(\w+\)'/;

commit ac712da46b046e6a655e9ff752674acbf3b819c3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:31:28 2008 +0000

    Factor out the "collection of rules" attribute into a role

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index d161a72..8fc3b37 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -12,6 +12,8 @@ use Path::Dispatcher::Path;
 use constant dispatch_class => 'Path::Dispatcher::Dispatch';
 use constant path_class     => 'Path::Dispatcher::Path';
 
+with 'Path::Dispatcher::Role::Rules';
+
 has name => (
     is      => 'rw',
     isa     => 'Str',
@@ -24,18 +26,6 @@ has name => (
     },
 );
 
-has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    init_arg  => 'rules',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
-);
-
 sub dispatch {
     my $self = shift;
     my $path = shift;
diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
new file mode 100644
index 0000000..ad7b7fe
--- /dev/null
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Role::Rules;
+use Moose::Role;
+
+has _rules => (
+    metaclass => 'Collection::Array',
+    is        => 'rw',
+    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
+    init_arg  => 'rules',
+    default   => sub { [] },
+    provides  => {
+        push     => 'add_rule',
+        elements => 'rules',
+    },
+);
+
+1;
+
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 9e7e5dc..790c383 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -3,7 +3,9 @@ package Path::Dispatcher::Rule::Under;
 use Moose;
 use Moose::Util::TypeConstraints;
 use MooseX::AttributeHelpers;
+
 extends 'Path::Dispatcher::Rule';
+with 'Path::Dispatcher::Role::Rules';
 
 subtype 'Path::Dispatcher::PrefixRule'
      => as 'Path::Dispatcher::Rule'
@@ -15,18 +17,6 @@ has predicate => (
     isa => 'Path::Dispatcher::PrefixRule',
 );
 
-has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    init_arg  => 'rules',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
-);
-
 sub match {
     my $self = shift;
     my $path = shift;

commit 91bf5fafd7d0338770b7448e470f39ed8d11309c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:34:05 2008 +0000

    Implementation of an "intersection" rule; all its rules must match

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index c860b64..b5aee16 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -64,6 +64,7 @@ require Path::Dispatcher::Rule::Always;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Dispatch;
 require Path::Dispatcher::Rule::Empty;
+require Path::Dispatcher::Rule::Intersection;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
 require Path::Dispatcher::Rule::Under;
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
new file mode 100644
index 0000000..a9fae01
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -0,0 +1,40 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Intersection;
+use Moose;
+use MooseX::AttributeHelpers;
+extends 'Path::Dispatcher::Rule';
+
+with 'Path::Dispatcher::Role::Rules';
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    for my $rule ($self->rules) {
+        return 0 unless $rule->match($path);
+    }
+
+    return 1;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Intersection - all rules must match
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 ATTRIBUTES
+
+=head2 rules
+
+=cut
+

commit d14ca8fd8dc0e9d02b9340ebf52a8a5b762e417c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:39:32 2008 +0000

    Tests for intersections

diff --git a/t/017-intersection.t b/t/017-intersection.t
new file mode 100644
index 0000000..53946bf
--- /dev/null
+++ b/t/017-intersection.t
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 3;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new(
+    rules => [
+        Path::Dispatcher::Rule::Intersection->new(
+            rules => [
+                Path::Dispatcher::Rule::Tokens->new(
+                    tokens => ['foo'],
+                    block => sub { push @calls, 'tokens' },
+                ),
+                Path::Dispatcher::Rule::Regex->new(
+                    regex => qr/^foo$/,
+                    block => sub { push @calls, 'regex' },
+                ),
+            ],
+            block => sub { push @calls, 'intersection' },
+        ),
+    ],
+);
+
+$dispatcher->run("foo");
+is_deeply([splice @calls], ['intersection'], "the intersection matched; doesn't automatically run the subrules");
+
+$dispatcher->run("food");
+is_deeply([splice @calls], [], "each subrule of the intersection must match");
+
+$dispatcher->run(" foo ");
+is_deeply([splice @calls], [], "each subrule of the intersection must match");
+

commit 877ca7f1fd617d107eb632c0ee3c757f80359ee2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:50:20 2008 +0000

    First pass at a Metadata matching rule

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index b5aee16..2658aec 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -65,6 +65,7 @@ require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Dispatch;
 require Path::Dispatcher::Rule::Empty;
 require Path::Dispatcher::Rule::Intersection;
+require Path::Dispatcher::Rule::Metadata;
 require Path::Dispatcher::Rule::Regex;
 require Path::Dispatcher::Rule::Tokens;
 require Path::Dispatcher::Rule::Under;
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
new file mode 100644
index 0000000..8b69b30
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -0,0 +1,43 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Metadata;
+use Moose;
+use MooseX::AttributeHelpers;
+extends 'Path::Dispatcher::Rule';
+
+has match_metadata => (
+    metaclass => 'Collection::Hash',
+    is        => 'rw',
+    isa       => 'HashRef',
+    required  => 1,
+    provides  => {
+        keys => 'metadata_keys',
+        get  => 'metadata',
+    },
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    my $path_metadata = $path->metadata;
+
+    for my $key ($self->metadata_keys) {
+        return 0 if !exists($path_metadata->{$key});
+
+        $self->_match_metadatum($path_metadata, $self->metadata($key))
+            or return 0;
+    }
+
+    return 1, $path->path;
+}
+
+sub _match_metadatum {
+    my $self     = shift;
+    my $got      = shift;
+    my $expected = shift;
+
+    return $got eq $expected;
+}
+
+1;
+

commit b5a3550e2cd7a9e375845abe6cb6472af5b11ca3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:55:12 2008 +0000

    Pass in the right value

diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 8b69b30..a3f1650 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -24,7 +24,7 @@ sub _match {
     for my $key ($self->metadata_keys) {
         return 0 if !exists($path_metadata->{$key});
 
-        $self->_match_metadatum($path_metadata, $self->metadata($key))
+        $self->_match_metadatum($path_metadata->{$key}, $self->metadata($key))
             or return 0;
     }
 

commit d66b8ce901548ecc8527a1b1189205226d55a7b8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 00:57:42 2008 +0000

    Path->get_metadata (which can be overridden by subclasses!)

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index b723ea4..4e5f751 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -1,6 +1,7 @@
 #!/usr/bin/env perl
 package Path::Dispatcher::Path;
 use Moose;
+use MooseX::AttributeHelpers;
 
 has path => (
     is        => 'rw',
@@ -9,9 +10,13 @@ has path => (
 );
 
 has metadata => (
+    metaclass => 'Collection::Hash',
     is        => 'rw',
     isa       => 'HashRef',
     predicate => 'has_metadata',
+    provides  => {
+        get => 'get_metadata',
+    },
 );
 
 # allow Path::Dispatcher::Path->new($path)

commit 8ff35e2ec1475d563f3126f256f21377f50dcb89
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 01:00:28 2008 +0000

    Make metadata only match a single metadata value

diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index a3f1650..6c38564 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -4,40 +4,27 @@ use Moose;
 use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
-has match_metadata => (
-    metaclass => 'Collection::Hash',
-    is        => 'rw',
-    isa       => 'HashRef',
-    required  => 1,
-    provides  => {
-        keys => 'metadata_keys',
-        get  => 'metadata',
-    },
+has name => (
+    is       => 'rw',
+    isa      => 'Str',
+    required => 1,
+);
+
+has value => (
+    is       => 'rw',
+    isa      => 'Str',
+    required => 1,
 );
 
 sub _match {
     my $self = shift;
     my $path = shift;
+    my $got = $path->get_metadata($self->name);
 
-    my $path_metadata = $path->metadata;
-
-    for my $key ($self->metadata_keys) {
-        return 0 if !exists($path_metadata->{$key});
-
-        $self->_match_metadatum($path_metadata->{$key}, $self->metadata($key))
-            or return 0;
-    }
+    return 0 if $self->value ne $got;
 
     return 1, $path->path;
 }
 
-sub _match_metadatum {
-    my $self     = shift;
-    my $got      = shift;
-    my $expected = shift;
-
-    return $got eq $expected;
-}
-
 1;
 

commit f22e353e05d758f36a9b373c5bf9effd4f705477
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 01:01:04 2008 +0000

    Begin writing tests for metadata

diff --git a/t/018-metadata.t b/t/018-metadata.t
new file mode 100644
index 0000000..1fb341a
--- /dev/null
+++ b/t/018-metadata.t
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new(
+    rules => [
+        Path::Dispatcher::Rule::Metadata->new(
+            name  => "http_method",
+            value => "GET",
+            block => sub { push @calls, $_ },
+        ),
+    ],
+);
+
+$dispatcher->run(Path::Dispatcher::Path->new(
+    path     => "the path",
+    metadata => {
+        http_method => "GET",
+    },
+));
+
+is_deeply([splice @calls], ["the path"]);
+
+$dispatcher->run(Path::Dispatcher::Path->new(
+    path     => "the path",
+    metadata => {
+        http_method => "POST",
+    },
+));
+
+is_deeply([splice @calls], [], "metadata didn't match");

commit ee81ec7f88b74c1c2d360db21096f463640c77f3
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 01:07:08 2008 +0000

    Simple rule: Eq, which tests: path eq $string

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 2658aec..e811dde 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -64,6 +64,7 @@ require Path::Dispatcher::Rule::Always;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Dispatch;
 require Path::Dispatcher::Rule::Empty;
+require Path::Dispatcher::Rule::Eq;
 require Path::Dispatcher::Rule::Intersection;
 require Path::Dispatcher::Rule::Metadata;
 require Path::Dispatcher::Rule::Regex;
diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
new file mode 100644
index 0000000..d61d54c
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+package Path::Dispatcher::Rule::Eq;
+use Moose;
+extends 'Path::Dispatcher::Rule';
+
+has string => (
+    is       => 'rw',
+    isa      => 'Str',
+    required => 1,
+);
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+
+    return $path->path eq $self->string;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Regex - predicate is a regular expression
+
+=head1 SYNOPSIS
+
+    my $rule = Path::Dispatcher::Rule::Regex->new(
+        regex => qr{^/comment(s?)/(\d+)$},
+        block => sub { display_comment($2) },
+    );
+
+=head1 DESCRIPTION
+
+Rules of this class use a regular expression to match against the path.
+
+=head1 ATTRIBUTES
+
+=head2 regex
+
+The regular expression to match against the path. It works just as you'd expect!
+
+The results are the capture variables (C<$1>, C<$2>, etc) and when the
+resulting L<Path::Dispatcher::Match> is executed, the codeblock will see these
+values. C<$`>, C<$&>, and C<$'> are not (yet) restored.
+
+=cut
+

commit 2b2fe703841227178e5cf20a284ec5d8dee04776
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 01:08:14 2008 +0000

    Refactor Metadata to take a rule for matching the metadata

diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 6c38564..2bf88cd 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -10,9 +10,9 @@ has name => (
     required => 1,
 );
 
-has value => (
+has matcher => (
     is       => 'rw',
-    isa      => 'Str',
+    isa      => 'Path::Dispatcher::Rule',
     required => 1,
 );
 
@@ -21,7 +21,9 @@ sub _match {
     my $path = shift;
     my $got = $path->get_metadata($self->name);
 
-    return 0 if $self->value ne $got;
+    # wow, offensive.. but powerful
+    my $faux_path = Path::Dispatcher::Path->new(path => $got);
+    return 0 unless $self->matcher->match($faux_path);
 
     return 1, $path->path;
 }
diff --git a/t/018-metadata.t b/t/018-metadata.t
index 1fb341a..c9f3a1f 100644
--- a/t/018-metadata.t
+++ b/t/018-metadata.t
@@ -9,9 +9,9 @@ my @calls;
 my $dispatcher = Path::Dispatcher->new(
     rules => [
         Path::Dispatcher::Rule::Metadata->new(
-            name  => "http_method",
-            value => "GET",
-            block => sub { push @calls, $_ },
+            name    => "http_method",
+            matcher => Path::Dispatcher::Rule::Eq->new(string => "GET"),
+            block   => sub { push @calls, $_ },
         ),
     ],
 );

commit df0a144b7659677cd1e1f4b800f642c8a1d65f02
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 02:21:55 2008 +0000

    Test to make sure that path + metadata matching works, as that'll be the most common use case for intersections

diff --git a/t/019-intersection-metadata.t b/t/019-intersection-metadata.t
new file mode 100644
index 0000000..1edbe3c
--- /dev/null
+++ b/t/019-intersection-metadata.t
@@ -0,0 +1,42 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my $dispatcher = Path::Dispatcher->new(
+    rules => [
+        Path::Dispatcher::Rule::Intersection->new(
+            block => sub { "creating a ticket" },
+            rules => [
+                Path::Dispatcher::Rule::Tokens->new(
+                    delimiter => '/',
+                    tokens    => ['=', 'model', 'Ticket'],
+                ),
+                Path::Dispatcher::Rule::Metadata->new(
+                    name    => 'http_method',
+                    matcher => Path::Dispatcher::Rule::Eq->new(string => 'POST'),
+                ),
+            ],
+        ),
+    ],
+);
+
+my @results = $dispatcher->run(Path::Dispatcher::Path->new(
+    path     => "/=/model/Ticket",
+    metadata => {
+        http_method => "POST",
+    },
+));
+
+is_deeply(\@results, ["creating a ticket"], "matched path and metadata");
+
+ at results = $dispatcher->run(Path::Dispatcher::Path->new(
+    path => "/=/model/Ticket.yml",
+    metadata => {
+        http_method => "GET",
+    },
+));
+
+is_deeply(\@results, [], "didn't match metadata");
+

commit b049ec9c8df44c326edcd5b992a20b3237f08669
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 18:17:35 2008 +0000

    Let rules have an optional name, for tracing

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index e811dde..97ecf8e 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -18,6 +18,12 @@ has prefix => (
     default => 0,
 );
 
+has name => (
+    is        => 'rw',
+    isa       => 'Str',
+    predicate => 'has_name',
+);
+
 sub match {
     my $self = shift;
     my $path = shift;

commit 3f8ea7503e18f240a152084b59ffe67c60fe54a6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 18:17:41 2008 +0000

    Begin implementing a debugging tracer

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 97ecf8e..60779fa 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -29,7 +29,12 @@ sub match {
     my $path = shift;
 
     my ($result, $leftover) = $self->_match($path);
-    return unless $result;
+
+    if (!$result) {
+        $self->trace(leftover => $leftover, match => undef, path => $path)
+            if $ENV{'PATH_DISPATCHER_TRACE'};
+        return;
+    }
 
     $leftover = '' if !defined($leftover);
 
@@ -51,6 +56,8 @@ sub match {
         leftover => $leftover,
     );
 
+    $self->trace(match => $match) if $ENV{'PATH_DISPATCHER_TRACE'};
+
     return $match;
 }
 
@@ -62,6 +69,26 @@ sub run {
     $self->block->(@_);
 }
 
+sub trace {
+    my $self = shift;
+    my %args = @_;
+    my $trace = "$self";
+    $trace .= " (" . $self->name . ")" if $self->has_name;
+
+    if (my $match = $args{match}) {
+        $trace .= " matched against (" . $match->path->path . ")";
+        $trace .= " with (" . $match->leftover . ") left over"
+            if length($match->leftover);
+    }
+    else {
+        $trace .= " did not match against (" . $args{path} . ")";
+    }
+
+    $trace .= ".\n";
+
+    warn $trace;
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit b8d48bc2dd1ca334f231d2ca946850a205d51e42
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 18:23:13 2008 +0000

    Declarative dispatcher sets rule name

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 7bfce0a..8b51f8c 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -191,15 +191,22 @@ sub _add_rule {
         $rule = shift;
     }
 
+    # XXX: caller level should be closer to $Test::Builder::Level
+    my (undef, $file, $line) = caller(1);
+    my $rule_name = "$file:$line";
+
     if (!defined(wantarray)) {
         if ($UNDER_RULE) {
             $UNDER_RULE->add_rule($rule);
+            $rule->name($UNDER_RULE->name . " (rule $rule_name)");
         }
         else {
             $self->dispatcher->add_rule($rule);
+            $rule->name($self->dispatcher->name . " (rule $rule_name)");
         }
     }
     else {
+        $rule->name($rule_name);
         return $rule, @_;
     }
 }

commit a8d1ee6634fd41e4c7b40ecac44adb45667ad33c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 18:36:50 2008 +0000

    Trace running codeblocks

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 956eaaa..928fad5 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -32,6 +32,9 @@ sub run {
         eval {
             local $SIG{__DIE__} = 'DEFAULT';
 
+            $match->rule->trace(running => 1, match => $match)
+                if $ENV{PATH_DISPATCHER_TRACE};
+
             push @results, scalar $match->run(@args);
 
             die "Path::Dispatcher abort\n"
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 60779fa..ac7f9c4 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -72,10 +72,15 @@ sub run {
 sub trace {
     my $self = shift;
     my %args = @_;
+    my $match = $args{match};
+
     my $trace = "$self";
     $trace .= " (" . $self->name . ")" if $self->has_name;
 
-    if (my $match = $args{match}) {
+    if ($args{running}) {
+        $trace .= " running codeblock with path (" . $match->path->path . ")";
+    }
+    elsif ($match) {
         $trace .= " matched against (" . $match->path->path . ")";
         $trace .= " with (" . $match->leftover . ") left over"
             if length($match->leftover);

commit 669bf56127bccbabcc8800bbc3fbb34c0ee149c0
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 18:41:18 2008 +0000

    Don't require under rules be named when naming subrules

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 8b51f8c..286ec91 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -198,7 +198,12 @@ sub _add_rule {
     if (!defined(wantarray)) {
         if ($UNDER_RULE) {
             $UNDER_RULE->add_rule($rule);
-            $rule->name($UNDER_RULE->name . " (rule $rule_name)");
+
+            my $full_name = $UNDER_RULE->has_name
+                          ? $UNDER_RULE->name . " (rule $rule_name)"
+                          : "anonymous Under (rule $rule_name)";
+
+            $rule->name($full_name);
         }
         else {
             $self->dispatcher->add_rule($rule);

commit cd6c4fb58a564cb21cde67188372090af4834177
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 20:53:34 2008 +0000

    Cleanup

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index ac7f9c4..d7d2f8e 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -70,23 +70,25 @@ sub run {
 }
 
 sub trace {
-    my $self = shift;
-    my %args = @_;
+    my $self  = shift;
+    my %args  = @_;
+
     my $match = $args{match};
+    my $path  = $match ? $match->path->path : $args{path}->path;
 
     my $trace = "$self";
     $trace .= " (" . $self->name . ")" if $self->has_name;
 
     if ($args{running}) {
-        $trace .= " running codeblock with path (" . $match->path->path . ")";
+        $trace .= " running codeblock with path ($path)";
     }
     elsif ($match) {
-        $trace .= " matched against (" . $match->path->path . ")";
+        $trace .= " matched against ($path)";
         $trace .= " with (" . $match->leftover . ") left over"
             if length($match->leftover);
     }
     else {
-        $trace .= " did not match against (" . $args{path} . ")";
+        $trace .= " did not match against ($path)";
     }
 
     $trace .= ".\n";

commit 065e51efd18c042530123cc21d6f519adcaddd97
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 21:21:23 2008 +0000

    Add the under rule earlier so it gets a name for subrules to use

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 286ec91..9dca17b 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -99,12 +99,12 @@ sub build_sugar {
                 predicate => $predicate,
             );
 
+            $into->_add_rule($under, @_);
+
             do {
                 local $UNDER_RULE = $under;
                 $rules->();
             };
-
-            $into->_add_rule($under, @_);
         },
         redispatch_to => sub {
             my ($dispatcher) = @_;

commit 2693a75ed04b619049914ce6a38d348d30981476
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 21:59:09 2008 +0000

    Include "readable attributes" in the trace (regex, tokens, etc)

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index d7d2f8e..62e02b6 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -69,6 +69,8 @@ sub run {
     $self->block->(@_);
 }
 
+sub readable_attributes { }
+
 sub trace {
     my $self  = shift;
     my %args  = @_;
@@ -76,9 +78,15 @@ sub trace {
     my $match = $args{match};
     my $path  = $match ? $match->path->path : $args{path}->path;
 
+    # name
     my $trace = "$self";
     $trace .= " (" . $self->name . ")" if $self->has_name;
 
+    # attributes such as tokens or regex
+    my $attr = $self->readable_attributes;
+    $trace .= " $attr" if defined($attr) && length($attr);
+
+    # what just happened
     if ($args{running}) {
         $trace .= " running codeblock with path ($path)";
     }

commit 25b6ab701bb67fcd6eb73a94f3fabaf8b665f656
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 21:59:13 2008 +0000

    Begin giving rules readable_attributes

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index d22070e..471cb39 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -17,6 +17,8 @@ sub match {
     return $dispatch->matches;
 }
 
+sub readable_attributes { shift->dispatcher->name }
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 no Moose::Util::TypeConstraints;
diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
index d61d54c..89b6e41 100644
--- a/lib/Path/Dispatcher/Rule/Eq.pm
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -16,6 +16,8 @@ sub _match {
     return $path->path eq $self->string;
 }
 
+sub readable_attributes { q{"} . shift->string . q{"} }
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 2bf88cd..497a26f 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -28,5 +28,15 @@ sub _match {
     return 1, $path->path;
 }
 
+sub readable_attributes {
+    my $self = shift;
+    return sprintf "{ '%s': %s }",
+        $self->name,
+        $self->matcher->readable_attributes;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Moose;
+
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index f42c4a2..4cc57b9 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -26,6 +26,8 @@ sub _match {
     return \@matches;
 }
 
+sub readable_attributes { shift->regex }
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 790c383..1d5efef 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -30,6 +30,8 @@ sub match {
     return grep { defined } map { $_->match($new_path) } $self->rules;
 }
 
+sub readable_attributes { shift->predicate->readable_attributes }
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 no Moose::Util::TypeConstraints;

commit 3c7c4e343d4b1d0eed85906cea947792e40bdd03
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 22:02:53 2008 +0000

    Rename the metadata key from "name" to "field" because "name" is now used for all rules

diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 497a26f..54a9027 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -4,7 +4,7 @@ use Moose;
 use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
-has name => (
+has field => (
     is       => 'rw',
     isa      => 'Str',
     required => 1,
@@ -19,7 +19,7 @@ has matcher => (
 sub _match {
     my $self = shift;
     my $path = shift;
-    my $got = $path->get_metadata($self->name);
+    my $got = $path->get_metadata($self->field);
 
     # wow, offensive.. but powerful
     my $faux_path = Path::Dispatcher::Path->new(path => $got);
@@ -31,7 +31,7 @@ sub _match {
 sub readable_attributes {
     my $self = shift;
     return sprintf "{ '%s': %s }",
-        $self->name,
+        $self->field,
         $self->matcher->readable_attributes;
 }
 
diff --git a/t/018-metadata.t b/t/018-metadata.t
index c9f3a1f..079f00d 100644
--- a/t/018-metadata.t
+++ b/t/018-metadata.t
@@ -9,7 +9,7 @@ my @calls;
 my $dispatcher = Path::Dispatcher->new(
     rules => [
         Path::Dispatcher::Rule::Metadata->new(
-            name    => "http_method",
+            field   => "http_method",
             matcher => Path::Dispatcher::Rule::Eq->new(string => "GET"),
             block   => sub { push @calls, $_ },
         ),
diff --git a/t/019-intersection-metadata.t b/t/019-intersection-metadata.t
index 1edbe3c..ab25efc 100644
--- a/t/019-intersection-metadata.t
+++ b/t/019-intersection-metadata.t
@@ -14,7 +14,7 @@ my $dispatcher = Path::Dispatcher->new(
                     tokens    => ['=', 'model', 'Ticket'],
                 ),
                 Path::Dispatcher::Rule::Metadata->new(
-                    name    => 'http_method',
+                    field   => 'http_method',
                     matcher => Path::Dispatcher::Rule::Eq->new(string => 'POST'),
                 ),
             ],

commit 8587929915ebc579839d707904efd6b38d74a508
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 22:22:06 2008 +0000

    Don't include the stringified rule if we have a name

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 62e02b6..12c5e3c 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -79,8 +79,13 @@ sub trace {
     my $path  = $match ? $match->path->path : $args{path}->path;
 
     # name
-    my $trace = "$self";
-    $trace .= " (" . $self->name . ")" if $self->has_name;
+    my $trace = '';
+    if ($self->has_name) {
+        $trace .= $self->name;
+    }
+    else {
+        $trace .= "$self";
+    }
 
     # attributes such as tokens or regex
     my $attr = $self->readable_attributes;

commit 84b119a0784972bc72b45d8640c223635c8efec9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 22:22:09 2008 +0000

    Slightly better rule name formatting (parent - rule file:line)

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 9dca17b..4f51c0c 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -200,14 +200,14 @@ sub _add_rule {
             $UNDER_RULE->add_rule($rule);
 
             my $full_name = $UNDER_RULE->has_name
-                          ? $UNDER_RULE->name . " (rule $rule_name)"
-                          : "anonymous Under (rule $rule_name)";
+                          ? "(" . $UNDER_RULE->name . " - rule $rule_name)"
+                          : "(anonymous Under - rule $rule_name)";
 
             $rule->name($full_name);
         }
         else {
             $self->dispatcher->add_rule($rule);
-            $rule->name($self->dispatcher->name . " (rule $rule_name)");
+            $rule->name("(" . $self->dispatcher->name . " - rule $rule_name)");
         }
     }
     else {

commit fe94644041fcf9f12709044dc4e2e0aab9441629
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 22:23:13 2008 +0000

    Trace levels; 2 turns on readable_attributes, 10 turns on deparsing of run blocks

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 12c5e3c..f7ce8b3 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -75,6 +75,11 @@ sub trace {
     my $self  = shift;
     my %args  = @_;
 
+    my $level = $ENV{'PATH_DISPATCHER_TRACE'};
+
+    return if exists($args{level})
+           && $level < $args{level};
+
     my $match = $args{match};
     my $path  = $match ? $match->path->path : $args{path}->path;
 
@@ -88,12 +93,18 @@ sub trace {
     }
 
     # attributes such as tokens or regex
-    my $attr = $self->readable_attributes;
-    $trace .= " $attr" if defined($attr) && length($attr);
+    if ($level >= 2) {
+        my $attr = $self->readable_attributes;
+        $trace .= " $attr" if defined($attr) && length($attr);
+    }
 
     # what just happened
     if ($args{running}) {
         $trace .= " running codeblock with path ($path)";
+        if ($level >= 10) {
+            require B::Deparse;
+            $trace .= ": " . B::Deparse->new->coderef2text($match->rule->block);
+        }
     }
     elsif ($match) {
         $trace .= " matched against ($path)";

commit 36a42738b15dbe127b0be54c849565f3a3d9b0f4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Nov 19 23:04:22 2008 +0000

    Token match tracing

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 8e98fe8..972faf2 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -56,13 +56,32 @@ sub _match {
     my @matched;
 
     for my $expected ($self->tokens) {
-        return unless @tokens; # too few words
+        unless (@tokens) {
+            $self->trace(no_tokens => 1, on_token => $expected, path => $path)
+                if $ENV{'PATH_DISPATCHER_TRACE'};
+            return;
+        }
+
         my $got = shift @tokens;
-        return unless $self->_match_token($got, $expected);
+
+        unless ($self->_match_token($got, $expected)) {
+            $self->trace(
+                no_match  => 1,
+                got_token => $got,
+                on_token  => $expected,
+                path      => $path,
+            ) if $ENV{'PATH_DISPATCHER_TRACE'};
+            return;
+        }
+
         push @matched, $got;
     }
 
-    return if @tokens && !$self->prefix;
+    if (@tokens && !$self->prefix) {
+        $self->trace(tokens_left => \@tokens, path => $path)
+            if $ENV{'PATH_DISPATCHER_TRACE'};
+        return;
+    }
 
     my $leftover = $self->untokenize(@tokens);
     return \@matched, $leftover;
@@ -102,6 +121,28 @@ sub untokenize {
     return join $self->delimiter, @tokens;
 }
 
+sub readable_attributes {
+}
+
+after trace => sub {
+    my $self = shift;
+    my %args = @_;
+
+    return if $ENV{'PATH_DISPATCHER_TRACE'} < 3;
+
+    if ($args{no_tokens}) {
+        warn "... We ran out of tokens when trying to match ($args{on_token}).\n";
+    }
+    elsif ($args{no_match}) {
+        my ($got, $expected) = @args{'got_token', 'on_token'};
+        warn "... Did not match ($got) against expected ($expected).\n";
+    }
+    elsif ($args{tokens_left}) {
+        my @tokens = @{ $args{tokens_left} };
+        warn "... We ran out of path tokens, expecting (@tokens).\n";
+    }
+};
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 no Moose::Util::TypeConstraints;

commit 97a5285052adedc8c1a79f250f8401acf9b7020e
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Dec 3 00:54:24 2008 +0000

    See also: HTTPx-Dispatcher

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 8fc3b37..e7ade21 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -176,6 +176,8 @@ L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Path-Dispatcher>.
 
 =item L<Catalyst::Dispatcher>
 
+=item L<HTTPx::Dispatcher>
+
 =item L<Mojolicious::Dispatcher>
 
 =item L<Path::Router>

commit 4851c304fc3097ca1bf2899204bd60071f879c39
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Dec 11 03:30:38 2008 +0000

    use the Eq rule for string predicates

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 4f51c0c..411d5f7 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -152,10 +152,9 @@ my %rule_creators = (
         ),
     },
     '' => sub {
-        my ($self, $tokens, $block) = @_;
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => [$tokens],
-            delimiter => $self->token_delimiter,
+        my ($self, $string, $block) = @_;
+        Path::Dispatcher::Rule::Eq->new(
+            string => $string,
             $block ? (block => $block) : (),
         ),
     },

commit f5883334ab208028267523b96a36efc65e934ab9
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Dec 11 03:30:42 2008 +0000

    Prefix matching for Eq rules

diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
index 89b6e41..cdc2cee 100644
--- a/lib/Path/Dispatcher/Rule/Eq.pm
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -13,7 +13,12 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    return $path->path eq $self->string;
+    return $path->path eq $self->string unless $self->prefix;
+
+    my $truncated = substr($path->path, 0, length($self->string));
+    return 0 unless $truncated eq $self->string;
+
+    return (1, substr($path->path, length($self->string)));
 }
 
 sub readable_attributes { q{"} . shift->string . q{"} }

commit 9ec40716238985a90721468fd7c5f5ee9b023ca5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Dec 11 03:30:50 2008 +0000

    Test fixes, this kind of sucks

diff --git a/t/016-more-under.t b/t/016-more-under.t
index 04763be..7777373 100644
--- a/t/016-more-under.t
+++ b/t/016-more-under.t
@@ -9,17 +9,17 @@ do {
     package Under::Where;
     use Path::Dispatcher::Declarative -base;
 
-    under 'ticket' => sub {
+    under 'ticket ' => sub {
         on 'create' => sub { push @calls, "ticket create" };
         on 'update' => sub { push @calls, "ticket update" };
     };
 
-    under 'blog' => sub {
-        under 'post' => sub {
+    under 'blog ' => sub {
+        under 'post ' => sub {
             on 'create' => sub { push @calls, "create blog post" };
             on 'delete' => sub { push @calls, "delete blog post" };
         };
-        under 'comment' => sub {
+        under 'comment ' => sub {
             on 'create' => sub { push @calls, "create blog comment" };
             on 'delete' => sub { push @calls, "delete blog comment" };
         };

commit 945249c1ca969aa3da8123a2a5cacd2b06015931
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Sat Dec 27 22:19:11 2008 +0000

    Revert that; tokens are more useful

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 411d5f7..2129add 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -153,8 +153,8 @@ my %rule_creators = (
     },
     '' => sub {
         my ($self, $string, $block) = @_;
-        Path::Dispatcher::Rule::Eq->new(
-            string => $string,
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => [$string],
             $block ? (block => $block) : (),
         ),
     },
@@ -273,11 +273,11 @@ Adds a rule to the dispatcher for the given path. The path may be:
 =item a string
 
 This is taken to mean a single token; creates an
-L<Path::Dispatcher::Rule::Token> rule.
+L<Path::Dispatcher::Rule::Tokens> rule.
 
 =item an array reference
 
-This is creates a L<Path::Dispatcher::Rule::Token> rule.
+This is creates a L<Path::Dispatcher::Rule::Tokens> rule.
 
 =item a regular expression
 
diff --git a/t/016-more-under.t b/t/016-more-under.t
index 7777373..04763be 100644
--- a/t/016-more-under.t
+++ b/t/016-more-under.t
@@ -9,17 +9,17 @@ do {
     package Under::Where;
     use Path::Dispatcher::Declarative -base;
 
-    under 'ticket ' => sub {
+    under 'ticket' => sub {
         on 'create' => sub { push @calls, "ticket create" };
         on 'update' => sub { push @calls, "ticket update" };
     };
 
-    under 'blog ' => sub {
-        under 'post ' => sub {
+    under 'blog' => sub {
+        under 'post' => sub {
             on 'create' => sub { push @calls, "create blog post" };
             on 'delete' => sub { push @calls, "delete blog post" };
         };
-        under 'comment ' => sub {
+        under 'comment' => sub {
             on 'create' => sub { push @calls, "create blog comment" };
             on 'delete' => sub { push @calls, "delete blog comment" };
         };

commit 944697a13132c8e9498f71cd11b23859a4e05743
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 01:36:42 2008 +0000

    Tokens' readable_attributes

diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 972faf2..035abf2 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -122,6 +122,28 @@ sub untokenize {
 }
 
 sub readable_attributes {
+    my $self = shift;
+
+    my $deserialize;
+    $deserialize = sub {
+        my $ret = '';
+        for (my $i = 0; $i < @_; ++$i) {
+            local $_ = $_[$i];
+
+            if (ref($_) eq 'ARRAY') {
+                $ret .= $deserialize->(@$_);
+            }
+            else {
+                $ret .= $_;
+            }
+
+            $ret .= ',' if $i + 1 < @_;
+        }
+
+        return "[" . $ret . "]";
+    };
+
+    return $deserialize->($self->tokens);
 }
 
 after trace => sub {

commit 5004a0e5446f20dec3bd2025d8eb4eec9b64c28a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 01:58:50 2008 +0000

    Doh! Forgot to include the delimiter in the non-ref rules. (Also case sensitivity.. whimper)

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 2129add..0ee4026 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -137,6 +137,17 @@ my %rule_creators = (
             $block ? (block => $block) : (),
         ),
     },
+    '' => sub {
+        my ($self, $string, $block) = @_;
+        my $case_sensitive = $self->case_sensitive_tokens;
+
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => [$string],
+            delimiter => $self->token_delimiter,
+            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
+            $block ? (block => $block) : (),
+        ),
+    },
     CODE => sub {
         my ($self, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(
@@ -151,13 +162,6 @@ my %rule_creators = (
             $block ? (block => $block) : (),
         ),
     },
-    '' => sub {
-        my ($self, $string, $block) = @_;
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => [$string],
-            $block ? (block => $block) : (),
-        ),
-    },
     empty => sub {
         my ($self, $undef, $block) = @_;
         Path::Dispatcher::Rule::Empty->new(

commit a44135eca509108ad89b85b8f35a2bb641e9dce6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 02:01:51 2008 +0000

    Rename the empty rule test from its old name, "always"

diff --git a/t/105-always.t b/t/105-empty.t
similarity index 100%
rename from t/105-always.t
rename to t/105-empty.t

commit 143fd0ed5bb2ee111082669b507a9bf68e231054
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 02:09:27 2008 +0000

    TODO tests for under dwimming wrt path

diff --git a/t/200-under-next_rule.t b/t/200-under-next_rule.t
new file mode 100644
index 0000000..67e1fc1
--- /dev/null
+++ b/t/200-under-next_rule.t
@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+my @calls;
+
+do {
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    under first => sub {
+        on qr/./ => sub {
+            push @calls, "[$_] first -> regex";
+            next_rule;
+        };
+
+        on second => sub {
+            push @calls, "[$_] first -> string, via next_rule";
+        };
+    };
+};
+
+TODO: {
+    local $TODO = "under doesn't pass its matched fragment as part of the path";
+    MyApp::Dispatcher->run("first second");
+    is_deeply([splice @calls], [
+        "[first second] first -> regex",
+        "[first second] first -> string, via next_rule",
+    ]);
+}
+

commit d6c8bb295e9823bf061711d877021ea175f85cc5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:20:15 2008 +0000

    Stringify Path::Dispatcher::Path into the path, so that wise users can call methods on it to get metadata etc

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index a0170d1..53bd313 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -45,7 +45,7 @@ sub run {
     my $self = shift;
     my @args = @_;
 
-    local $_ = $self->path->path;
+    local $_ = $self->path;
 
     if ($self->set_number_vars) {
         return $self->run_with_number_vars(
diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 4e5f751..76c868d 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -3,6 +3,8 @@ package Path::Dispatcher::Path;
 use Moose;
 use MooseX::AttributeHelpers;
 
+use overload q{""} => sub { shift->path };
+
 has path => (
     is        => 'rw',
     isa       => 'Str',
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index f7ce8b3..d9a2b4a 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -81,7 +81,7 @@ sub trace {
            && $level < $args{level};
 
     my $match = $args{match};
-    my $path  = $match ? $match->path->path : $args{path}->path;
+    my $path  = $match ? $match->path : $args{path};
 
     # name
     my $trace = '';
diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index bd8d6f3..4349127 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -13,7 +13,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    local $_ = $path->path;
+    local $_ = $path;
     return $self->matcher->($path);
 }
 
diff --git a/t/007-coderef-matcher.t b/t/007-coderef-matcher.t
index ad68f47..8823c9a 100644
--- a/t/007-coderef-matcher.t
+++ b/t/007-coderef-matcher.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 2;
+use Test::More tests => 3;
 use Path::Dispatcher;
 
 my (@matches, @calls);
@@ -19,3 +19,6 @@ $dispatcher->run('foobar');
 is_deeply([splice @matches], ['foobar']);
 is_deeply([splice @calls], [ [] ]);
 
+$dispatcher->run('other');
+is($matches[0]->path, 'other');
+

commit dd0316d29d3a6e51dc5c3d458a236d73e513284d
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:20:24 2008 +0000

    Ignore .prove

commit b006556c69f01e3f933cc98538fbc9e362fa2b65
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:20:27 2008 +0000

    Cleanup

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index e7ade21..c55da23 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -30,6 +30,7 @@ sub dispatch {
     my $self = shift;
     my $path = shift;
 
+    # Automatically box string paths
     if (!ref($path)) {
         $path = $self->path_class->new(
             path => $path,
@@ -53,12 +54,10 @@ sub dispatch_rule {
     my $self = shift;
     my %args = @_;
 
-    my @matches = $args{rule}->match($args{path})
-        or return 0;
-
+    my @matches = $args{rule}->match($args{path});
     $args{dispatch}->add_matches(@matches);
 
-    return 1;
+    return @matches;
 }
 
 sub run {

commit 0f0da5539ef7f0da44644b9d455a061f85716e88
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:20:31 2008 +0000

    Verbiage tweaks

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index c55da23..16efb1b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -63,6 +63,7 @@ sub dispatch_rule {
 sub run {
     my $self = shift;
     my $path = shift;
+
     my $dispatch = $self->dispatch($path);
 
     return $dispatch->run(@_);
@@ -75,7 +76,7 @@ sub import {
     my $package = caller;
 
     if (@_) {
-        Carp::croak "use Path::Dispatcher (@_) called by $package. Did you mean Path::Dispatcher::Declarative?";
+        Carp::croak "'use Path::Dispatcher (@_)' called by $package. Did you mean to use Path::Dispatcher::Declarative?";
     }
 }
 
@@ -124,7 +125,7 @@ that matched. These phases are distinct so that, if you need to, you can
 inspect which rules were matched without ever running their codeblocks.
 
 Most consumers would want to use L<Path::Dispatcher::Declarative> which gives
-you some sugar inspired by L<Jifty::Dispatcher>.
+you some sugar, inspired by L<Jifty::Dispatcher>.
 
 =head1 ATTRIBUTES
 
@@ -134,8 +135,8 @@ A list of L<Path::Dispatcher::Rule> objects.
 
 =head2 name
 
-A human-readable name; this will be used in the (currently nonexistent)
-debugging hooks.
+A human-readable name; this will be used in the debugging hooks. In
+L<Path::Dispatcher::Declarative>, this is the package name of the dispatcher.
 
 =head1 METHODS
 

commit 4efc616ec6dc334b1904b609f102541c445f628c
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:20:55 2008 +0000

    Remove shebangs; no need for them

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 16efb1b..2520446 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 0ee4026..9a28b71 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Declarative;
 use strict;
 use warnings;
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 928fad5..cb46ce1 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Dispatch;
 use Moose;
 use MooseX::AttributeHelpers;
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 53bd313..a1bb9f6 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Match;
 use Moose;
 
diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 76c868d..809a19f 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Path;
 use Moose;
 use MooseX::AttributeHelpers;
diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
index ad7b7fe..684d38a 100644
--- a/lib/Path/Dispatcher/Role/Rules.pm
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Role::Rules;
 use Moose::Role;
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index d9a2b4a..02c3516 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule;
 use Moose;
 
diff --git a/lib/Path/Dispatcher/Rule/Always.pm b/lib/Path/Dispatcher/Rule/Always.pm
index f2a347f..e02becd 100644
--- a/lib/Path/Dispatcher/Rule/Always.pm
+++ b/lib/Path/Dispatcher/Rule/Always.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Always;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index 4349127..7201fe9 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::CodeRef;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 471cb39..27c01af 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Dispatch;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/Empty.pm b/lib/Path/Dispatcher/Rule/Empty.pm
index fe9cff1..6175b31 100644
--- a/lib/Path/Dispatcher/Rule/Empty.pm
+++ b/lib/Path/Dispatcher/Rule/Empty.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Empty;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
index cdc2cee..e136e09 100644
--- a/lib/Path/Dispatcher/Rule/Eq.pm
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Eq;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
index a9fae01..c828f5b 100644
--- a/lib/Path/Dispatcher/Rule/Intersection.pm
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Intersection;
 use Moose;
 use MooseX::AttributeHelpers;
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 54a9027..3ccc29f 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Metadata;
 use Moose;
 use MooseX::AttributeHelpers;
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index 4cc57b9..dee7c34 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Regex;
 use Moose;
 extends 'Path::Dispatcher::Rule';
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 035abf2..2a9b004 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Tokens;
 use Moose;
 use Moose::Util::TypeConstraints;
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 1d5efef..cdf2a0e 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -1,4 +1,3 @@
-#!/usr/bin/env perl
 package Path::Dispatcher::Rule::Under;
 use Moose;
 use Moose::Util::TypeConstraints;

commit 9b2801bc71a38d64752df97c834153b4d0eef836
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:07 2008 +0000

    More cleanup

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index a1bb9f6..49ae2a4 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -63,14 +63,15 @@ sub run_with_number_vars {
     # we don't have direct write access to $1 and friends, so we have to
     # do this little hack. the only way we can update $1 is by matching
     # against a regex (5.10 fixes that)..
-    my $re = join '', map { "(\Q$_\E)" } @_;
-    my $str = join '', @_;
+    my $re  = join '', map { "(\Q$_\E)" } @_;
+    my $str = join '',                    @_;
 
     # we need to check length because Perl's annoying gotcha of the empty regex
     # actually being an alias for whatever the previously used regex was 
     # (useful last decade when qr// hadn't been invented)
     # we need to do the match anyway, because we have to clear the number vars
     ($str, $re) = ("x", "x") if length($str) == 0;
+
     $str =~ $re
         or die "Unable to match '$str' against a copy of itself ($re)!";
 
diff --git a/lib/Path/Dispatcher/Rule/Always.pm b/lib/Path/Dispatcher/Rule/Always.pm
index e02becd..dfa2d31 100644
--- a/lib/Path/Dispatcher/Rule/Always.pm
+++ b/lib/Path/Dispatcher/Rule/Always.pm
@@ -26,4 +26,3 @@ is returned as leftover.
 
 =cut
 
-

commit bdd28f3696130b5b80746ac79cfef8bac2cc40a1
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:11 2008 +0000

    Some doc for Path::Dispatcher::Path

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 809a19f..3c67866 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -37,3 +37,35 @@ no Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Path - path and some optional metadata
+
+=head1 SYNOPSIS
+
+    my $path = Path::Dispatcher::Path->new(
+        path     => "/REST/Ticket/1",
+        metadata => {
+            http_method => "DELETE",
+        },
+    );
+
+    $path->path;                        # /REST/Ticket/1
+    $path->get_metadata("http_method"); # DELETE
+
+=head1 ATTRIBUTES
+
+=head2 path
+
+A string representing the path. C<Path::Dispatcher::Path> is basically a boxed
+string. :)
+
+=head2 metadata
+
+A hash representing arbitrary metadata. The L<Path::Dispatcher::Rule::Metadata>
+rule is designed to match against members of this hash.
+
+=cut
+

commit 641d7825e6dba0bb9d29fede4286fa0e0f777891
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:19 2008 +0000

    On sufficiently high levels, dump CodeRef's matcher attribute too

diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index 7201fe9..575466e 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -16,6 +16,15 @@ sub _match {
     return $self->matcher->($path);
 }
 
+sub readable_attributes {
+    return if $ENV{'PATH_DISPATCHER_TRACE'} < 10;
+
+    my $self = shift;
+
+    require B::Deparse;
+    return B::Deparse->new->coderef2text($self->matcher);
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 

commit b46c5856fb6e4b5c2bb9542c193c362b26e16d22
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:40 2008 +0000

    Test fix, since error text changed

diff --git a/t/900-use-path-dispatcher.t b/t/900-use-path-dispatcher.t
index 2061345..dfd2f35 100644
--- a/t/900-use-path-dispatcher.t
+++ b/t/900-use-path-dispatcher.t
@@ -8,5 +8,5 @@ eval "
     use Path::Dispatcher -base;
 ";
 
-like($@, qr/^use Path::Dispatcher \(-base\) called by MyApp::Dispatcher\. Did you mean Path::Dispatcher::Declarative\?/);
+like($@, qr/^'use Path::Dispatcher \(-base\)' called by MyApp::Dispatcher\. Did you mean to use Path::Dispatcher::Declarative\?/);
 

commit c2b0e4bf35374d9d61ecd9e5e639a5a3eca563b7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:46 2008 +0000

    Lots of Changes!

diff --git a/Changes b/Changes
index 4f272a6..afecccd 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,24 @@
 Revision history for Path-Dispatcher
 
 0.07
+        Paths are now boxed with the new Path::Dispatcher::Path.
+
+        New rule type "Intersection" which matches only when each of its
+        subrules match.
+
+        New rule type "Metadata" which matches the optional metadata (a hash)
+        of the path.
+
+        New rule type "Eq" which just does basic string equality.
+
+        Rules can now be named. In Path::Dispatcher::Declarative, each rule is
+        named with its dispatcher's name and the file:line where the rule was
+        defined.
+
+        Dispatch tracing output, for debugging. Set environment variable
+        PATH_DISPATCHER_TRACE to a number. The higher the number, the more
+        output will be generated. The current maximum value of 10 will dump
+        code references.
 
 0.06 Sat Nov 15 21:02:29 2008
         New rule type "Empty" which matches only the empty path.

commit 2fb89a2bd14afc24d70d7f6b8e0f854390d2af54
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 19:21:56 2008 +0000

    Refactor cloning path objects but changing the path inside it into $path->clone_path($new_path)

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 3c67866..b00f68c 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -32,6 +32,13 @@ around BUILDARGS => sub {
     $self->$orig(@_);
 };
 
+sub clone_path {
+    my $self = shift;
+    my $path = shift;
+
+    return $self->meta->clone_instance($self, path => $path, @_);
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index 3ccc29f..bb956fd 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -21,8 +21,8 @@ sub _match {
     my $got = $path->get_metadata($self->field);
 
     # wow, offensive.. but powerful
-    my $faux_path = Path::Dispatcher::Path->new(path => $got);
-    return 0 unless $self->matcher->match($faux_path);
+    my $metadata_path = $path->clone_path($got);
+    return 0 unless $self->matcher->match($metadata_path);
 
     return 1, $path->path;
 }
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index cdf2a0e..1df1b25 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -23,8 +23,7 @@ sub match {
     my $prefix_match = $self->predicate->match($path)
         or return;
 
-    my $suffix = $prefix_match->leftover;
-    my $new_path = $path->meta->clone_instance($path, path => $suffix);
+    my $new_path = $path->clone_path($prefix_match->leftover);
 
     return grep { defined } map { $_->match($new_path) } $self->rules;
 }

commit 4ced901c8d742e95e4a9a48b4657e272b1c44281
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 20:37:06 2008 +0000

    Refactor to avoid code duplication

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 9a28b71..e331761 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -136,17 +136,6 @@ my %rule_creators = (
             $block ? (block => $block) : (),
         ),
     },
-    '' => sub {
-        my ($self, $string, $block) = @_;
-        my $case_sensitive = $self->case_sensitive_tokens;
-
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => [$string],
-            delimiter => $self->token_delimiter,
-            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
-            $block ? (block => $block) : (),
-        ),
-    },
     CODE => sub {
         my ($self, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(
@@ -173,8 +162,17 @@ sub _create_rule {
     my ($self, $stage, $matcher, $block) = @_;
 
     my $rule_creator;
-    $rule_creator   = $rule_creators{empty} if $matcher eq '';
-    $rule_creator ||= $rule_creators{ ref $matcher };
+
+    if ($matcher eq '') {
+        $rule_creator = $rule_creators{empty};
+    }
+    elsif (!ref($matcher)) {
+        $rule_creator = $rule_creators{ARRAY};
+        $matcher = [$matcher];
+    }
+    else {
+        $rule_creator = $rule_creators{ ref $matcher };
+    }
 
     $rule_creator or die "I don't know how to create a rule for type $matcher";
 

commit 947ece3f298a495ed557df4850cbe7beffed37b8
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Dec 30 21:31:15 2008 +0000

    Begin writing overview doc for Path::Dispatcher::Declarative

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index e331761..abf8fc0 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -245,6 +245,8 @@ Path::Dispatcher::Declarative - sugary dispatcher
 
 =head1 DESCRIPTION
 
+L<Jifty::Dispatcher> rocks!
+
 =head1 KEYWORDS
 
 =head2 dispatcher -> Dispatcher

commit 2921bfd3b7313ebaedce085d3e3e5b1c8f0327ac
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jan 28 06:25:42 2009 +0000

    Pass $stage to rule_creators

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index abf8fc0..51b412a 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -126,7 +126,7 @@ sub build_sugar {
 
 my %rule_creators = (
     ARRAY => sub {
-        my ($self, $tokens, $block) = @_;
+        my ($self, $stage, $tokens, $block) = @_;
         my $case_sensitive = $self->case_sensitive_tokens;
 
         Path::Dispatcher::Rule::Tokens->new(
@@ -137,21 +137,21 @@ my %rule_creators = (
         ),
     },
     CODE => sub {
-        my ($self, $matcher, $block) = @_;
+        my ($self, $stage, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(
             matcher => $matcher,
             $block ? (block => $block) : (),
         ),
     },
     Regexp => sub {
-        my ($self, $regex, $block) = @_;
+        my ($self, $stage, $regex, $block) = @_;
         Path::Dispatcher::Rule::Regex->new(
             regex => $regex,
             $block ? (block => $block) : (),
         ),
     },
     empty => sub {
-        my ($self, $undef, $block) = @_;
+        my ($self, $stage, $undef, $block) = @_;
         Path::Dispatcher::Rule::Empty->new(
             $block ? (block => $block) : (),
         ),
@@ -176,7 +176,7 @@ sub _create_rule {
 
     $rule_creator or die "I don't know how to create a rule for type $matcher";
 
-    return $rule_creator->($self, $matcher, $block);
+    return $rule_creator->($self, $stage, $matcher, $block);
 }
 
 sub _add_rule {

commit f4c7a4e69b5aaf93be9f0311fa1837e26b3274b4
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jan 28 06:25:56 2009 +0000

    Metadata sugar

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 51b412a..7e39937 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -136,6 +136,23 @@ my %rule_creators = (
             $block ? (block => $block) : (),
         ),
     },
+    HASH => sub {
+        my ($self, $stage, $metadata_matchers, $block) = @_;
+
+        if (keys %$metadata_matchers == 1) {
+            my ($field) = keys %$metadata_matchers;
+            my ($value) = values %$metadata_matchers;
+            my $matcher = $self->_create_rule($stage, $value);
+
+            return Path::Dispatcher::Rule::Metadata->new(
+                field   => $field,
+                matcher => $matcher,
+                $block ? (block => $block) : (),
+            );
+        }
+
+        die "Doesn't support multiple metadata rules yet";
+    },
     CODE => sub {
         my ($self, $stage, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(

commit a229f285b73bae1d2daec9f3c6706e628a5a7e72
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jan 28 06:26:04 2009 +0000

    Tests for metadata-matching sugar

diff --git a/t/106-metadata.t b/t/106-metadata.t
new file mode 100644
index 0000000..fcaa261
--- /dev/null
+++ b/t/106-metadata.t
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+my @calls;
+
+do {
+    package MyApp::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    on { method => 'GET' } => sub {
+        push @calls, "method: GET, path: $_";
+    };
+};
+
+my $path = Path::Dispatcher::Path->new(
+    path     => "/REST/Ticket/1.yml",
+    metadata => {
+        method => "GET",
+    },
+);
+
+MyApp::Dispatcher->run($path);
+is_deeply([splice @calls], ["method: GET, path: /REST/Ticket/1.yml"]);
+

commit f28a470601ae5d72c68961d460fd25c54f8f3873
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Jan 28 06:38:02 2009 +0000

    (Failing) tests for metadata deep data structures

diff --git a/t/106-metadata.t b/t/106-metadata.t
index fcaa261..b8d4c5e 100644
--- a/t/106-metadata.t
+++ b/t/106-metadata.t
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 my @calls;
 
@@ -18,9 +18,35 @@ my $path = Path::Dispatcher::Path->new(
     path     => "/REST/Ticket/1.yml",
     metadata => {
         method => "GET",
+        query_parameters => {
+            owner => 'Sartak',
+            status => 'closed',
+        },
     },
 );
 
 MyApp::Dispatcher->run($path);
 is_deeply([splice @calls], ["method: GET, path: /REST/Ticket/1.yml"]);
 
+do {
+    package MyApp::Other::Dispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    on {
+        query_parameters => {
+            owner => qr/^\w+$/,
+        },
+    } => sub {
+        push @calls, "query_parameters/owner/regex";
+    };
+};
+
+TODO: {
+    local $TODO = "metadata can't be a deep data structure";
+
+    eval {
+        MyApp::Other::Dispatcher->run($path);
+    };
+    is_deeply([splice @calls], ["query_parameters/owner/regex"]);
+};
+

commit 06b04f088392d22d7efaf49fa9ff15d8fec27a90
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 3 18:18:34 2009 +0000

    More Changes

diff --git a/Changes b/Changes
index afecccd..4c2c77b 100644
--- a/Changes
+++ b/Changes
@@ -1,13 +1,13 @@
 Revision history for Path-Dispatcher
 
-0.07
+0.07 Wed Jan 28 01:39:37 2009
         Paths are now boxed with the new Path::Dispatcher::Path.
 
         New rule type "Intersection" which matches only when each of its
         subrules match.
 
         New rule type "Metadata" which matches the optional metadata (a hash)
-        of the path.
+        of the path. The sugar for this is: on { foo => "bar" }
 
         New rule type "Eq" which just does basic string equality.
 

commit e3c5a76e2157653d5de28186214bcfdb18436f5b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 3 18:19:00 2009 +0000

    Bump to 0.08

diff --git a/Changes b/Changes
index 4c2c77b..a58f176 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.08
+
 0.07 Wed Jan 28 01:39:37 2009
         Paths are now boxed with the new Path::Dispatcher::Path.
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 2520446..9f22213 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -2,7 +2,7 @@ package Path::Dispatcher;
 use Moose;
 use MooseX::AttributeHelpers;
 
-our $VERSION = '0.07';
+our $VERSION = '0.08';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 3b766406abc78c5f7a31fe0db8b0ecfe9b07abbd
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 3 18:19:02 2009 +0000

    Inline uses of MooseX::AttributeHelpers

diff --git a/Changes b/Changes
index a58f176..7d772b5 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 Revision history for Path-Dispatcher
 
 0.08
+        Inline uses of MooseX::AttributeHelpers.
 
 0.07 Wed Jan 28 01:39:37 2009
         Paths are now boxed with the new Path::Dispatcher::Path.
diff --git a/Makefile.PL b/Makefile.PL
index 0d0ef67..d26d1c7 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -4,7 +4,6 @@ name           'Path-Dispatcher';
 all_from       'lib/Path/Dispatcher.pm';
 
 requires 'Moose';
-requires 'MooseX::AttributeHelpers';
 requires 'Sub::Exporter';
 
 build_requires 'Test::More';
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 9f22213..7dc3e9e 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,6 +1,5 @@
 package Path::Dispatcher;
 use Moose;
-use MooseX::AttributeHelpers;
 
 our $VERSION = '0.08';
 
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index cb46ce1..323951f 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -1,22 +1,28 @@
 package Path::Dispatcher::Dispatch;
 use Moose;
-use MooseX::AttributeHelpers;
 
 use Path::Dispatcher::Match;
 
 has _matches => (
-    metaclass => 'Collection::Array',
     is        => 'rw',
     isa       => 'ArrayRef[Path::Dispatcher::Match]',
     default   => sub { [] },
-    provides  => {
-        push     => 'add_match',
-        elements => 'matches',
-        count    => 'has_match',
-        first    => 'first_match',
-    },
 );
 
+sub add_match {
+    my $self = shift;
+
+    $_->isa('Path::Dispatcher::Match')
+        or confess "$_ is not a Path::Dispatcher::Match"
+            for @_;
+
+    push @{ $self->{matches} }, @_;
+}
+
+sub matches     { @{ shift->{matches} } }
+sub has_match   { scalar @{ shift->{matches} } }
+sub first_match { shift->{matches}[0] }
+
 # aliases
 __PACKAGE__->meta->add_method(add_matches => __PACKAGE__->can('add_match'));
 __PACKAGE__->meta->add_method(has_matches => __PACKAGE__->can('has_match'));
diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index b00f68c..5fbddc1 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -1,6 +1,5 @@
 package Path::Dispatcher::Path;
 use Moose;
-use MooseX::AttributeHelpers;
 
 use overload q{""} => sub { shift->path };
 
@@ -11,13 +10,9 @@ has path => (
 );
 
 has metadata => (
-    metaclass => 'Collection::Hash',
     is        => 'rw',
     isa       => 'HashRef',
     predicate => 'has_metadata',
-    provides  => {
-        get => 'get_metadata',
-    },
 );
 
 # allow Path::Dispatcher::Path->new($path)
@@ -39,6 +34,13 @@ sub clone_path {
     return $self->meta->clone_instance($self, path => $path, @_);
 }
 
+sub get_metadata {
+    my $self = shift;
+    my $name = shift;
+
+    return $self->metadata->{$name};
+}
+
 __PACKAGE__->meta->make_immutable;
 no Moose;
 
diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
index 684d38a..bc051f1 100644
--- a/lib/Path/Dispatcher/Role/Rules.pm
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -2,16 +2,23 @@ package Path::Dispatcher::Role::Rules;
 use Moose::Role;
 
 has _rules => (
-    metaclass => 'Collection::Array',
-    is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Rule]',
-    init_arg  => 'rules',
-    default   => sub { [] },
-    provides  => {
-        push     => 'add_rule',
-        elements => 'rules',
-    },
+    is       => 'rw',
+    isa      => 'ArrayRef[Path::Dispatcher::Rule]',
+    init_arg => 'rules',
+    default  => sub { [] },
 );
 
+sub add_rule {
+    my $self = shift;
+
+    $_->isa('Path::Dispatcher::Rule')
+        or confess "$_ is not a Path::Dispatcher::Rule"
+            for @_;
+
+    push @{ $self->{_rules} }, @_;
+}
+
+sub rules { @{ shift->{_rules} } }
+
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
index c828f5b..e309eb2 100644
--- a/lib/Path/Dispatcher/Rule/Intersection.pm
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -1,6 +1,5 @@
 package Path::Dispatcher::Rule::Intersection;
 use Moose;
-use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
 with 'Path::Dispatcher::Role::Rules';
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index bb956fd..bb71b70 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -1,6 +1,5 @@
 package Path::Dispatcher::Rule::Metadata;
 use Moose;
-use MooseX::AttributeHelpers;
 extends 'Path::Dispatcher::Rule';
 
 has field => (
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 1df1b25..554e18c 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -1,7 +1,6 @@
 package Path::Dispatcher::Rule::Under;
 use Moose;
 use Moose::Util::TypeConstraints;
-use MooseX::AttributeHelpers;
 
 extends 'Path::Dispatcher::Rule';
 with 'Path::Dispatcher::Role::Rules';

commit deff5d3738a034f9c0d2e885c43cd9e81fc9da66
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Feb 4 21:07:02 2009 +0000

    Depend on Moose::Util::TypeConstraints less

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 27c01af..1187cfa 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -20,7 +20,6 @@ sub readable_attributes { shift->dispatcher->name }
 
 __PACKAGE__->meta->make_immutable;
 no Moose;
-no Moose::Util::TypeConstraints;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 2a9b004..29124ee 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -1,36 +1,10 @@
 package Path::Dispatcher::Rule::Tokens;
 use Moose;
-use Moose::Util::TypeConstraints;
 extends 'Path::Dispatcher::Rule';
 
-# a token may be
-#   - a string
-#   - a regular expression
-
-# this will be extended to add
-#   - an array reference containing (alternations)
-#     - strings
-#     - regular expressions
-
-my $Str      = find_type_constraint('Str');
-my $Regex    = find_type_constraint('RegexpRef');
-my $ArrayRef = find_type_constraint('ArrayRef');
-
-my $Alternation;
-$Alternation = subtype as 'Defined'
-     => where {
-         return $Str->check($_) || $Regex->check($_) if ref($_) ne 'ARRAY';
-         $Alternation->check($_) or return for @$_;
-         1
-     };
-
-subtype 'Path::Dispatcher::Tokens'
-     => as 'ArrayRef'
-     => where { $Alternation->check($_) or return for @$_; 1 };
-
 has tokens => (
     is         => 'rw',
-    isa        => 'Path::Dispatcher::Tokens',
+    isa        => 'ArrayRef',
     auto_deref => 1,
     required   => 1,
 );
@@ -91,16 +65,16 @@ sub _match_token {
     my $got      = shift;
     my $expected = shift;
 
-    if ($ArrayRef->check($expected)) {
+    if (!ref($expected)) {
+        ($got, $expected) = (lc $got, lc $expected) if !$self->case_sensitive;
+        return $got eq $expected;
+    }
+    elsif (ref($expected) eq 'ARRAY') {
         for my $alternative (@$expected) {
             return 1 if $self->_match_token($got, $alternative);
         }
     }
-    elsif ($Str->check($expected)) {
-        ($got, $expected) = (lc $got, lc $expected) if !$self->case_sensitive;
-        return $got eq $expected;
-    }
-    elsif ($Regex->check($expected)) {
+    elsif (ref($expected) eq 'Regexp') {
         return $got =~ $expected;
     }
     else {
@@ -166,7 +140,6 @@ after trace => sub {
 
 __PACKAGE__->meta->make_immutable;
 no Moose;
-no Moose::Util::TypeConstraints;
 
 1;
 
diff --git a/t/903-weird-token.t b/t/903-weird-token.t
deleted file mode 100644
index 127ba72..0000000
--- a/t/903-weird-token.t
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use Test::More tests => 2;
-use Path::Dispatcher;
-use Test::Exception;
-
-throws_ok {
-    Path::Dispatcher::Rule::Tokens->new(
-        tokens => [ 'foo', { bar => 1 }, 'baz' ],
-    )
-} qr/^Attribute \(tokens\) does not pass the type constraint because: Validation failed for 'Path::Dispatcher::Tokens' failed with value ARRAY\(\w+\)/;
-
-my $rule = Path::Dispatcher::Rule::Tokens->new(
-    tokens => [],
-);
-
-push @{ $rule->{tokens} }, { weird_token => 1 };
-
-throws_ok {
-    $rule->match(Path::Dispatcher::Path->new("mezzanine"));
-} qr/^Unexpected token 'HASH\(\w+\)'/;

commit 9207d0802269f31dc02d1e7f921838b4f992091a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Feb 4 21:11:30 2009 +0000

    Any::Moose-ify Path::Dispatcher

diff --git a/Makefile.PL b/Makefile.PL
index d26d1c7..b526bfd 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,7 +3,7 @@ use inc::Module::Install;
 name           'Path-Dispatcher';
 all_from       'lib/Path/Dispatcher.pm';
 
-requires 'Moose';
+requires 'Any::Moose';
 requires 'Sub::Exporter';
 
 build_requires 'Test::More';
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 7dc3e9e..3dd1234 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher;
-use Moose;
+use Any::Moose;
 
 our $VERSION = '0.08';
 
@@ -79,7 +79,7 @@ sub import {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 323951f..b425bc0 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -1,11 +1,11 @@
 package Path::Dispatcher::Dispatch;
-use Moose;
+use Any::Moose;
 
 use Path::Dispatcher::Match;
 
 has _matches => (
     is        => 'rw',
-    isa       => 'ArrayRef[Path::Dispatcher::Match]',
+    isa       => 'ArrayRef',
     default   => sub { [] },
 );
 
@@ -59,7 +59,7 @@ sub run {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 49ae2a4..5a67b09 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Match;
-use Moose;
+use Any::Moose;
 
 use Path::Dispatcher::Path;
 use Path::Dispatcher::Rule;
@@ -79,7 +79,7 @@ sub run_with_number_vars {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 5fbddc1..8993e11 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Path;
-use Moose;
+use Any::Moose;
 
 use overload q{""} => sub { shift->path };
 
@@ -42,7 +42,7 @@ sub get_metadata {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
index bc051f1..78f9b51 100644
--- a/lib/Path/Dispatcher/Role/Rules.pm
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -1,9 +1,9 @@
 package Path::Dispatcher::Role::Rules;
-use Moose::Role;
+use Any::Moose '::Role';
 
 has _rules => (
     is       => 'rw',
-    isa      => 'ArrayRef[Path::Dispatcher::Rule]',
+    isa      => 'ArrayRef',
     init_arg => 'rules',
     default  => sub { [] },
 );
@@ -20,5 +20,7 @@ sub add_rule {
 
 sub rules { @{ shift->{_rules} } }
 
+no Any::Moose;
+
 1;
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 02c3516..b449d66 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule;
-use Moose;
+use Any::Moose;
 
 use Path::Dispatcher::Match;
 
@@ -120,7 +120,7 @@ sub trace {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 # don't require others to load our subclasses explicitly
 require Path::Dispatcher::Rule::Always;
diff --git a/lib/Path/Dispatcher/Rule/Always.pm b/lib/Path/Dispatcher/Rule/Always.pm
index dfa2d31..8828f9e 100644
--- a/lib/Path/Dispatcher/Rule/Always.pm
+++ b/lib/Path/Dispatcher/Rule/Always.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Always;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 sub _match {
@@ -9,7 +9,7 @@ sub _match {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/CodeRef.pm b/lib/Path/Dispatcher/Rule/CodeRef.pm
index 575466e..451c79d 100644
--- a/lib/Path/Dispatcher/Rule/CodeRef.pm
+++ b/lib/Path/Dispatcher/Rule/CodeRef.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::CodeRef;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has matcher => (
@@ -26,7 +26,7 @@ sub readable_attributes {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 1187cfa..5e2e523 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Dispatch;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has dispatcher => (
@@ -19,7 +19,7 @@ sub match {
 sub readable_attributes { shift->dispatcher->name }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Empty.pm b/lib/Path/Dispatcher/Rule/Empty.pm
index 6175b31..a026139 100644
--- a/lib/Path/Dispatcher/Rule/Empty.pm
+++ b/lib/Path/Dispatcher/Rule/Empty.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Empty;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 sub _match {
@@ -10,7 +10,7 @@ sub _match {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
index e136e09..db75875 100644
--- a/lib/Path/Dispatcher/Rule/Eq.pm
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Eq;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has string => (
@@ -23,7 +23,7 @@ sub _match {
 sub readable_attributes { q{"} . shift->string . q{"} }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Intersection.pm b/lib/Path/Dispatcher/Rule/Intersection.pm
index e309eb2..afc6ad2 100644
--- a/lib/Path/Dispatcher/Rule/Intersection.pm
+++ b/lib/Path/Dispatcher/Rule/Intersection.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Intersection;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 with 'Path::Dispatcher::Role::Rules';
@@ -16,7 +16,7 @@ sub _match {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index bb71b70..fc67e9f 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Metadata;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has field => (
@@ -34,7 +34,7 @@ sub readable_attributes {
 }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index dee7c34..676371a 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Regex;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has regex => (
@@ -28,7 +28,7 @@ sub _match {
 sub readable_attributes { shift->regex }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 29124ee..791c37e 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -1,5 +1,5 @@
 package Path::Dispatcher::Rule::Tokens;
-use Moose;
+use Any::Moose;
 extends 'Path::Dispatcher::Rule';
 
 has tokens => (
@@ -139,7 +139,7 @@ after trace => sub {
 };
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
+no Any::Moose;
 
 1;
 
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index 554e18c..c1ebd38 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -1,6 +1,6 @@
 package Path::Dispatcher::Rule::Under;
-use Moose;
-use Moose::Util::TypeConstraints;
+use Any::Moose;
+use Any::Moose '::Util::TypeConstraints';
 
 extends 'Path::Dispatcher::Rule';
 with 'Path::Dispatcher::Role::Rules';
@@ -30,8 +30,7 @@ sub match {
 sub readable_attributes { shift->predicate->readable_attributes }
 
 __PACKAGE__->meta->make_immutable;
-no Moose;
-no Moose::Util::TypeConstraints;
+no Any::Moose;
 
 1;
 

commit b1e6e3d95602e6ea4e5569aec260540d885cf4f6
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Feb 5 00:47:30 2009 +0000

    The actual error is that Mouse doesn't support custom TC failure messages; that's acceptable for now

diff --git a/t/012-under.t b/t/012-under.t
index 048b743..24174db 100644
--- a/t/012-under.t
+++ b/t/012-under.t
@@ -84,4 +84,4 @@ eval {
         ),
     );
 };
-like($@, qr/Attribute \(predicate\) does not pass the type constraint because: This rule \(Path::Dispatcher::Rule::Tokens=HASH\(0x\w+\)\) does not match just prefixes!/, "predicate MUST match just a prefix");
+like($@, qr/Attribute \(predicate\) does not pass the type constraint /, "predicate MUST match just a prefix");

commit eaaad67a793973ade8da61b45f67f3b43f407a2b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Feb 5 00:47:34 2009 +0000

    Update Changes for Any::Moose

diff --git a/Changes b/Changes
index 7d772b5..9feae99 100644
--- a/Changes
+++ b/Changes
@@ -3,6 +3,8 @@ Revision history for Path-Dispatcher
 0.08
         Inline uses of MooseX::AttributeHelpers.
 
+        Now use "Any::Moose" (basically Squirrel done right)
+
 0.07 Wed Jan 28 01:39:37 2009
         Paths are now boxed with the new Path::Dispatcher::Path.
 

commit da344eeea3e53127dff5ec97270d223535e3127a
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Feb 5 17:15:56 2009 +0000

    Changes time

diff --git a/Changes b/Changes
index 9feae99..67782a2 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,6 @@
 Revision history for Path-Dispatcher
 
-0.08
+0.08 Thu Feb 5 12:15:38 2009
         Inline uses of MooseX::AttributeHelpers.
 
         Now use "Any::Moose" (basically Squirrel done right)

commit 3465c8dd8ef3167364e6948b5340450364e261f2
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Thu Feb 5 17:18:21 2009 +0000

    Bump to 0.09

diff --git a/Changes b/Changes
index 67782a2..40635d3 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.09
+
 0.08 Thu Feb 5 12:15:38 2009
         Inline uses of MooseX::AttributeHelpers.
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 3dd1234..bac9ddb 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,7 @@
 package Path::Dispatcher;
 use Any::Moose;
 
-our $VERSION = '0.08';
+our $VERSION = '0.09';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 6f86fc8d7a986d39926c8c8d309dfcfcbf32bd1b
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 10 02:11:29 2009 +0000

    Avoid using method modifiers since it'd be a new dep when Mouse is used

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 8993e11..36215e2 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -16,15 +16,14 @@ has metadata => (
 );
 
 # allow Path::Dispatcher::Path->new($path)
-around BUILDARGS => sub {
-    my $orig = shift;
+sub BUILDARGS {
     my $self = shift;
 
     if (@_ == 1 && !ref($_[0])) {
         unshift @_, 'path';
     }
 
-    $self->$orig(@_);
+    $self->SUPER::BUILDARGS(@_);
 };
 
 sub clone_path {
diff --git a/lib/Path/Dispatcher/Rule/Tokens.pm b/lib/Path/Dispatcher/Rule/Tokens.pm
index 791c37e..e9c6059 100644
--- a/lib/Path/Dispatcher/Rule/Tokens.pm
+++ b/lib/Path/Dispatcher/Rule/Tokens.pm
@@ -119,10 +119,12 @@ sub readable_attributes {
     return $deserialize->($self->tokens);
 }
 
-after trace => sub {
+sub trace {
     my $self = shift;
     my %args = @_;
 
+    $self->SUPER::trace(@_);
+
     return if $ENV{'PATH_DISPATCHER_TRACE'} < 3;
 
     if ($args{no_tokens}) {

commit 5f0296014632ea6743c2f725c541183d6262dad7
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 10 02:12:30 2009 +0000

    Update Changes

diff --git a/Changes b/Changes
index 40635d3..70a4ab9 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 Revision history for Path-Dispatcher
 
-0.09
+0.09 Mon Feb 9 21:12:18 2009
+        Avoid using method modifiers since it's potentially another dep.
 
 0.08 Thu Feb 5 12:15:38 2009
         Inline uses of MooseX::AttributeHelpers.

commit aea994838c69d3357072132d84c46b309e095dfb
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 10 02:13:43 2009 +0000

    Bump to 0.10

diff --git a/Changes b/Changes
index 70a4ab9..9873f60 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.10
+
 0.09 Mon Feb 9 21:12:18 2009
         Avoid using method modifiers since it's potentially another dep.
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index bac9ddb..3220d20 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,7 @@
 package Path::Dispatcher;
 use Any::Moose;
 
-our $VERSION = '0.09';
+our $VERSION = '0.10';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit a6098d30e8dc40f7c0d82ff301f879ea33578618
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Tue Feb 10 02:18:31 2009 +0000

    Update copyright year

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 3220d20..1cee37c 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -184,7 +184,7 @@ L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Path-Dispatcher>.
 
 =head1 COPYRIGHT & LICENSE
 
-Copyright 2008 Best Practical Solutions.
+Copyright 2008-2009 Best Practical Solutions.
 
 This program is free software; you can redistribute it and/or modify it
 under the same terms as Perl itself.

commit 6f75ab8fcc5ab4d3925b82bf32b99327b5caa6d5
Author: sartak <sartak at e417ac7c-1bcc-0310-8ffa-8f5827389a85>
Date:   Wed Feb 11 02:02:09 2009 +0000

    No need to futz with @- and @+, since a regex match in list context returns $1, $2, ...

diff --git a/lib/Path/Dispatcher/Rule/Regex.pm b/lib/Path/Dispatcher/Rule/Regex.pm
index 676371a..baec99a 100644
--- a/lib/Path/Dispatcher/Rule/Regex.pm
+++ b/lib/Path/Dispatcher/Rule/Regex.pm
@@ -12,9 +12,7 @@ sub _match {
     my $self = shift;
     my $path = shift;
 
-    return unless $path->path =~ $self->regex;
-
-    my @matches = map { substr($path->path, $-[$_], $+[$_] - $-[$_]) } 1 .. $#-;
+    return unless my @matches = $path->path =~ $self->regex;
 
     # if $' is in the program at all, then it slows down every single regex
     # we only want to include it if we have to

commit 1d4bc0e655735e220b47dc50e638c8e41701e386
Author: Shawn M Moore <sartak at gmail.com>
Date:   Mon Feb 23 21:23:21 2009 -0500

    Doc typo fix

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 7e39937..9ff8e72 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -312,7 +312,7 @@ This is creates a L<Path::Dispatcher::Rule::CodeRef> rule.
 =head2 under path => sub {}
 
 Creates a L<Path::Dispatcher::Rule::Under> rule. The contents of the coderef
-should be other L</on> and C<under> calls.
+should be nothing other L</on> and C<under> calls.
 
 =cut
 

commit f0fdf855d1439ac518add5d5ce08df83d56acedf
Author: robertkrimen <robertkrimen at gmail.com>
Date:   Mon Feb 23 18:28:58 2009 -0800

    Added missing -base to use in SYNOPSIS

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 9ff8e72..705f409 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -244,7 +244,7 @@ Path::Dispatcher::Declarative - sugary dispatcher
 =head1 SYNOPSIS
 
     package MyApp::Dispatcher;
-    use Path::Dispatcher::Declarative;
+    use Path::Dispatcher::Declarative -base;
 
     on score => sub { show_score() };
     

commit dbcb7b92a48f3d9a6a6fc970f7c62ce82174b24e
Author: Shawn M Moore <sartak at gmail.com>
Date:   Mon Feb 23 21:50:59 2009 -0500

    Eq just had Regex's doc copied over, fix.

diff --git a/lib/Path/Dispatcher/Rule/Eq.pm b/lib/Path/Dispatcher/Rule/Eq.pm
index db75875..684a7e7 100644
--- a/lib/Path/Dispatcher/Rule/Eq.pm
+++ b/lib/Path/Dispatcher/Rule/Eq.pm
@@ -31,28 +31,22 @@ __END__
 
 =head1 NAME
 
-Path::Dispatcher::Rule::Regex - predicate is a regular expression
+Path::Dispatcher::Rule::Eq - predicate is a string equality
 
 =head1 SYNOPSIS
 
-    my $rule = Path::Dispatcher::Rule::Regex->new(
-        regex => qr{^/comment(s?)/(\d+)$},
-        block => sub { display_comment($2) },
+    my $rule = Path::Dispatcher::Rule::Eq->new(
+        string => 'comment',
+        block  => sub { display_comment($2) },
     );
 
 =head1 DESCRIPTION
 
-Rules of this class use a regular expression to match against the path.
+Rules of this class simply check whether the string is equal to the path.
 
 =head1 ATTRIBUTES
 
-=head2 regex
-
-The regular expression to match against the path. It works just as you'd expect!
-
-The results are the capture variables (C<$1>, C<$2>, etc) and when the
-resulting L<Path::Dispatcher::Match> is executed, the codeblock will see these
-values. C<$`>, C<$&>, and C<$'> are not (yet) restored.
+=head2 string
 
 =cut
 

commit f216c9288a09d4da55e82dbba3022cdd513a6a24
Author: Shawn M Moore <sartak at gmail.com>
Date:   Mon Feb 23 21:54:05 2009 -0500

    Doc for Path::Dispatcher::Role::Rules

diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
index 78f9b51..8e6ba1b 100644
--- a/lib/Path/Dispatcher/Role/Rules.pm
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -24,3 +24,25 @@ no Any::Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Role::Rules - "has a list of rules"
+
+=head1 DESCRIPTION
+
+Classes that compose this role get the following things:
+
+=head1 ATTRIBUTES
+
+=head2 _rules
+
+=head1 METHODS
+
+=head2 rules
+
+=head2 add_rule
+
+=cut
+

commit 7852bd21ba06b07ba499aeb7c443bf0c712fb5e2
Author: Shawn M Moore <sartak at gmail.com>
Date:   Mon Feb 23 21:58:38 2009 -0500

    Doc for Metadata rule

diff --git a/lib/Path/Dispatcher/Rule/Metadata.pm b/lib/Path/Dispatcher/Rule/Metadata.pm
index fc67e9f..357ceff 100644
--- a/lib/Path/Dispatcher/Rule/Metadata.pm
+++ b/lib/Path/Dispatcher/Rule/Metadata.pm
@@ -38,3 +38,41 @@ no Any::Moose;
 
 1;
 
+__END__
+
+=head1 NAME
+
+Path::Dispatcher::Rule::Metadata - match path's metadata
+
+=head1 SYNOPSIS
+
+    my $path = Path::Dispatcher::Path->new(
+        path => '/REST/Ticket'
+        metadata => {
+            http_method => 'POST',
+        },
+    );
+
+    my $rule = Path::Dispatcher::Rule::Metadata->new(
+        field   => 'http_method',
+        matcher => Path::Dispatcher::Rule::Eq->new(string => 'POST'),
+    );
+
+    $rule->run($path);
+
+=head1 DESCRIPTION
+
+Rules of this class match the metadata portion of a path.
+
+=head1 ATTRIBUTES
+
+=head2 field
+
+The metadata field/key name.
+
+=head2 matcher
+
+A L<Path::Dispatcher::Rule> object for matching against the value of the field.
+
+=cut
+

commit 46f66f1b1a95cf1c9181107c433e7a418459adab
Author: Shawn M Moore <sartak at gmail.com>
Date:   Mon Feb 23 22:02:05 2009 -0500

    Amend synopsis to include "extensible" :)

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 1cee37c..448a710 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -87,7 +87,7 @@ __END__
 
 =head1 NAME
 
-Path::Dispatcher - flexible dispatch
+Path::Dispatcher - flexible and extensible dispatch
 
 =head1 SYNOPSIS
 

commit 06e9d853108a27f771f0b909e6ed78fd31da2c58
Author: robertkrimen <robertkrimen at gmail.com>
Date:   Tue Feb 24 12:41:01 2009 -0800

    Added slash-path-delimeter recipe w/test
    Added chaining recipe w/test
    (tenative) Added 'then' sugar to ::Declarative w/test & dox

diff --git a/Changes b/Changes
index 9873f60..20c29a6 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,9 @@
 Revision history for Path-Dispatcher
 
 0.10
+        Added slash-path-delimeter recipe
+        Added chaining recipe
+        (tenative) Added 'then' sugar to ::Declarative
 
 0.09 Mon Feb 9 21:12:18 2009
         Avoid using method modifiers since it's potentially another dep.
diff --git a/lib/Path/Dispatcher/Cookbook.pod b/lib/Path/Dispatcher/Cookbook.pod
new file mode 100644
index 0000000..0649adf
--- /dev/null
+++ b/lib/Path/Dispatcher/Cookbook.pod
@@ -0,0 +1,53 @@
+=pod
+
+=head1 NAME
+
+Path::Dispatcher::Cookbook - A cookbook for Path::Dispatcher
+
+=head1 RECIPES
+
+=head2 How can I change the path delimiter from a space ' ' to a slash '/'?
+
+In your Dispatcher object, define the C<token_delimiter> subroutine to return a slash '/':
+
+    package MyDispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    sub token_delimiter { '/' } # Or whatever delimiter you want to use
+
+=head2 How can I do rule chaining (like in Catalyst)?
+
+You can use a L<Path::Dispatcher::Rule::Always> rule in combination with C<next_rule> to get chaining behavior:
+
+    package MyDispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    under show => sub {
+        $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
+            Path::Dispatcher::Rule::Always->new(
+                stage => 'on',
+                block  => sub {
+                    print "Displaying ";
+                    next_rule;
+                },
+            ),
+        );
+        on inventory => sub {
+            print "inventory:\n";
+            ...
+        };
+        on score => sub {
+            print "score:\n";
+            ...
+        };
+    };
+
+    package main;
+
+    MyDispatcher->run("display inventory"); # "Displaying inventory\n ..."
+
+    MyDispatcher->run("display score"); # "Displaying score\n ..."
+
+It's a little bit ugly, but it works
+
+=cut
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 705f409..b3b4792 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -19,6 +19,14 @@ my $exporter = Sub::Exporter::build_exporter({
 sub token_delimiter { ' ' }
 sub case_sensitive_tokens { undef }
 
+sub _next_rule() {
+    die "Path::Dispatcher next rule\n";
+}
+
+sub _last_rule() {
+    die "Path::Dispatcher abort rule\n";
+}
+
 sub import {
     my $self = shift;
     my $pkg  = caller;
@@ -88,6 +96,17 @@ sub build_sugar {
         after => sub {
             $into->_add_rule('after_on', @_);
         },
+        then => sub (;&) {
+            my $block = shift;
+            my $rule = Path::Dispatcher::Rule::Always->new(
+                stage => 'on',
+                block  => sub {
+                    $block->(@_);
+                    _next_rule;
+                },
+            );
+            $into->_add_rule($rule);
+        },
         under => sub {
             my ($matcher, $rules) = @_;
 
@@ -119,8 +138,8 @@ sub build_sugar {
 
             $into->_add_rule($redispatch);
         },
-        next_rule => sub { die "Path::Dispatcher next rule\n" },
-        last_rule => sub { die "Path::Dispatcher abort\n" },
+        next_rule => \&_next_rule,
+        last_rule => \&_last_rule,
     };
 }
 
@@ -314,5 +333,29 @@ This is creates a L<Path::Dispatcher::Rule::CodeRef> rule.
 Creates a L<Path::Dispatcher::Rule::Under> rule. The contents of the coderef
 should be nothing other L</on> and C<under> calls.
 
+#=head2 then sub { }
+
+#Creates a L<Path::Dispatcher::Rule::Always> rule that will continue on to the
+#next rule via C<next_rule>
+
+#The only argument is a coderef that processes normally (like L<on>)
+
+#NOTE: You *can* avoid running a following rule by uysing C<abort_rule>
+
+#An example:
+
+#    under show => sub {
+#        then {
+#            print "Displaying ";
+#        };
+#        on inventory => sub {
+#            print "inventory:\n";
+#            ...
+#        };
+#        on score => sub {
+#            print "score:\n";
+#            ...
+#        };
+
 =cut
 
diff --git a/t/100-declarative.t b/t/100-declarative.t
index dc059a2..779fa17 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -1,7 +1,8 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 7;
+use Test::More tests => 11;
+#use Test::More plan => qw/no_plan/;
 
 my @calls;
 
@@ -19,6 +20,38 @@ do {
 
     rewrite quux => 'bar';
     rewrite qr/^quux-(.*)/ => sub { "bar:$1" };
+
+    on alpha => sub {
+        push @calls, "alpha"
+    };
+
+    under alpha => sub {
+    #    $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
+    #        Path::Dispatcher::Rule::Always->new(
+    #            stage => 'on',
+    #            block  => sub {
+    #                print "alpha (chain) ";
+    #                next_rule;
+    #            },
+    #        ),
+    #    );
+        then {
+            push @calls, "alpha (chain) "; 
+        };
+        on one => sub {
+            push @calls, "one";
+        };
+
+        then {
+            push @calls, "(before two or three) ";
+        };
+        on two => sub {
+            push @calls, "two";
+        };
+        on three => sub {
+            push @calls, "three";
+        };
+    };
 };
 
 ok(MyApp::Dispatcher->isa('Path::Dispatcher::Declarative'), "use Path::Dispatcher::Declarative sets up ISA");
@@ -47,3 +80,14 @@ is_deeply([splice @calls], [
 MyApp::Dispatcher->run('Token Matching');
 is_deeply([splice @calls], [], "token matching is by default case sensitive");
 
+MyApp::Dispatcher->run('alpha');
+is_deeply([splice @calls], ['alpha']);
+
+MyApp::Dispatcher->run('alpha one');
+is_deeply([splice @calls], ['alpha (chain) ', 'one']);
+
+MyApp::Dispatcher->run('alpha two');
+is_deeply([splice @calls], ['alpha (chain) ', '(before two or three) ', 'two']);
+
+MyApp::Dispatcher->run('alpha three');
+is_deeply([splice @calls], ['alpha (chain) ', '(before two or three) ', 'three']);
diff --git a/t/800-cb-slash-path-delimeter.t b/t/800-cb-slash-path-delimeter.t
new file mode 100644
index 0000000..de87290
--- /dev/null
+++ b/t/800-cb-slash-path-delimeter.t
@@ -0,0 +1,33 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+#use Test::More tests => 11;
+use Test::More; plan qw/no_plan/;
+
+my @result;
+
+do {
+    package MyDispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    sub token_delimiter { '/' }
+
+    under show => sub {
+        on inventory => sub {
+            push @result, "inventory";
+        };
+        on score => sub {
+            push @result, "score";
+        };
+    };
+};
+
+MyDispatcher->run('show/inventory');
+is_deeply([splice @result], ['inventory']);
+
+MyDispatcher->run('show/score');
+is_deeply([splice @result], ['score']);
+
+MyDispatcher->run('show inventory');
+is_deeply([splice @result], []);
+
diff --git a/t/801-cb-chaining.t b/t/801-cb-chaining.t
new file mode 100644
index 0000000..d9b991f
--- /dev/null
+++ b/t/801-cb-chaining.t
@@ -0,0 +1,41 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+#use Test::More tests => 11;
+use Test::More; plan qw/no_plan/;
+
+my @result;
+
+do {
+    package MyDispatcher;
+    use Path::Dispatcher::Declarative -base;
+
+    under show => sub {
+        $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
+            Path::Dispatcher::Rule::Always->new(
+                stage => 'on',
+                block  => sub {
+                    push @result, "Displaying";
+                    next_rule;
+                },
+            ),
+        );
+        on inventory => sub {
+            push @result, "inventory";
+        };
+        on score => sub {
+            push @result, "score";
+        };
+    };
+};
+
+MyDispatcher->run('show inventory');
+is_deeply([splice @result], ['Displaying', 'inventory']);
+
+MyDispatcher->run('show score');
+is_deeply([splice @result], ['Displaying', 'score']);
+
+MyDispatcher->run('show');
+is_deeply([splice @result], ['Displaying']); # This is kinda weird
+
+

commit 9cf0fb9b151916d6ea664d1db30373a67f42c56b
Merge: 46f66f1 06e9d85
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 15:45:30 2009 -0500

    Merge branch 'master' of git://github.com/robertkrimen/path-dispatcher


commit df36c0b25b74253ee3d07e6849a90790ff648416
Author: robertkrimen <robertkrimen at gmail.com>
Date:   Tue Feb 24 12:47:56 2009 -0800

    Don't display 'then' dox

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index b3b4792..03d5fa7 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -333,6 +333,8 @@ This is creates a L<Path::Dispatcher::Rule::CodeRef> rule.
 Creates a L<Path::Dispatcher::Rule::Under> rule. The contents of the coderef
 should be nothing other L</on> and C<under> calls.
 
+=cut
+
 #=head2 then sub { }
 
 #Creates a L<Path::Dispatcher::Rule::Always> rule that will continue on to the
@@ -357,5 +359,3 @@ should be nothing other L</on> and C<under> calls.
 #            ...
 #        };
 
-=cut
-

commit 1f9c5def8061271bc131a73e1796cd0ad7ccf9b8
Merge: 9cf0fb9 df36c0b
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 15:51:03 2009 -0500

    Merge branch 'master' of git://github.com/robertkrimen/path-dispatcher


commit a658d28a3c6da24bd52ded2ba10dca6f06038bb6
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 15:53:53 2009 -0500

    Minor fixes

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 03d5fa7..e09fa8d 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -19,11 +19,11 @@ my $exporter = Sub::Exporter::build_exporter({
 sub token_delimiter { ' ' }
 sub case_sensitive_tokens { undef }
 
-sub _next_rule() {
+sub _next_rule () {
     die "Path::Dispatcher next rule\n";
 }
 
-sub _last_rule() {
+sub _last_rule () {
     die "Path::Dispatcher abort rule\n";
 }
 
@@ -96,11 +96,11 @@ sub build_sugar {
         after => sub {
             $into->_add_rule('after_on', @_);
         },
-        then => sub (;&) {
+        then => sub (&) {
             my $block = shift;
             my $rule = Path::Dispatcher::Rule::Always->new(
                 stage => 'on',
-                block  => sub {
+                block => sub {
                     $block->(@_);
                     _next_rule;
                 },

commit 0c749f2bbf013bf00e44a67432bef1afbd7a1996
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 15:58:07 2009 -0500

    Update the Cookbook with "then", some verbiage

diff --git a/lib/Path/Dispatcher/Cookbook.pod b/lib/Path/Dispatcher/Cookbook.pod
index 0649adf..65f04e2 100644
--- a/lib/Path/Dispatcher/Cookbook.pod
+++ b/lib/Path/Dispatcher/Cookbook.pod
@@ -17,21 +17,15 @@ In your Dispatcher object, define the C<token_delimiter> subroutine to return a
 
 =head2 How can I do rule chaining (like in Catalyst)?
 
-You can use a L<Path::Dispatcher::Rule::Always> rule in combination with C<next_rule> to get chaining behavior:
+You can use a C<then> rule approximate chaining behavior:
 
     package MyDispatcher;
     use Path::Dispatcher::Declarative -base;
 
     under show => sub {
-        $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
-            Path::Dispatcher::Rule::Always->new(
-                stage => 'on',
-                block  => sub {
-                    print "Displaying ";
-                    next_rule;
-                },
-            ),
-        );
+        then {
+            print "Displaying ";
+        };
         on inventory => sub {
             print "inventory:\n";
             ...
@@ -48,6 +42,4 @@ You can use a L<Path::Dispatcher::Rule::Always> rule in combination with C<next_
 
     MyDispatcher->run("display score"); # "Displaying score\n ..."
 
-It's a little bit ugly, but it works
-
 =cut

commit e8d21f934e6362c11b8be6ba3987c5dce1ef0a29
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 15:59:57 2009 -0500

    Uncomment "then"'s doc

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index e09fa8d..4b6bcd5 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -333,29 +333,29 @@ This is creates a L<Path::Dispatcher::Rule::CodeRef> rule.
 Creates a L<Path::Dispatcher::Rule::Under> rule. The contents of the coderef
 should be nothing other L</on> and C<under> calls.
 
-=cut
+=head2 then sub { }
 
-#=head2 then sub { }
+Creates a L<Path::Dispatcher::Rule::Always> rule that will continue on to the
+next rule via C<next_rule>
 
-#Creates a L<Path::Dispatcher::Rule::Always> rule that will continue on to the
-#next rule via C<next_rule>
+The only argument is a coderef that processes normally (like L<on>).
 
-#The only argument is a coderef that processes normally (like L<on>)
+NOTE: You *can* avoid running a following rule by using C<abort_rule>.
 
-#NOTE: You *can* avoid running a following rule by uysing C<abort_rule>
+An example:
 
-#An example:
+    under show => sub {
+        then {
+            print "Displaying ";
+        };
+        on inventory => sub {
+            print "inventory:\n";
+            ...
+        };
+        on score => sub {
+            print "score:\n";
+            ...
+        };
 
-#    under show => sub {
-#        then {
-#            print "Displaying ";
-#        };
-#        on inventory => sub {
-#            print "inventory:\n";
-#            ...
-#        };
-#        on score => sub {
-#            print "score:\n";
-#            ...
-#        };
+=cut
 

commit 2fda2e776dcdda94fa54cc88312401dd5432a6c0
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 16:03:43 2009 -0500

    Use "then" in the chaining test

diff --git a/t/801-cb-chaining.t b/t/801-cb-chaining.t
index d9b991f..11e0d93 100644
--- a/t/801-cb-chaining.t
+++ b/t/801-cb-chaining.t
@@ -11,15 +11,9 @@ do {
     use Path::Dispatcher::Declarative -base;
 
     under show => sub {
-        $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
-            Path::Dispatcher::Rule::Always->new(
-                stage => 'on',
-                block  => sub {
-                    push @result, "Displaying";
-                    next_rule;
-                },
-            ),
-        );
+        then {
+            push @result, "Displaying";
+        };
         on inventory => sub {
             push @result, "inventory";
         };

commit 7e409b713f413af0085a39c2b5ab1bcf7c794dfe
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 16:07:16 2009 -0500

    Fix spelling of delimiter :)

diff --git a/t/800-cb-slash-path-delimeter.t b/t/800-cb-slash-path-delimiter.t
similarity index 100%
rename from t/800-cb-slash-path-delimeter.t
rename to t/800-cb-slash-path-delimiter.t

commit 20a9758ab9949e4bcc93f78b0e6dce855163558b
Author: Shawn M Moore <sartak at gmail.com>
Date:   Tue Feb 24 16:10:29 2009 -0500

    It's just "abort" not "abort rule"

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 4b6bcd5..3b3c625 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -24,7 +24,7 @@ sub _next_rule () {
 }
 
 sub _last_rule () {
-    die "Path::Dispatcher abort rule\n";
+    die "Path::Dispatcher abort\n";
 }
 
 sub import {

commit 395937a42e1445b5f6a615483ddd83e0e5c461f7
Author: robertkrimen <robertkrimen at gmail.com>
Date:   Tue Feb 24 23:46:25 2009 -0800

    Added rudimentary chaining support in the form of a new rule and a new
    declaration
    Modified ::Under to handle chaining

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 448a710..7b59b1c 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -53,6 +53,10 @@ sub dispatch_rule {
     my %args = @_;
 
     my @matches = $args{rule}->match($args{path});
+
+    # Support ::Chain here? Probably not. As ::Chain doesn't make sense unless it is within an ::Under
+#    return if $matches[-1]->rule->isa('Path::Dispatcher::Rule::Chain'); 
+    
     $args{dispatch}->add_matches(@matches);
 
     return @matches;
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 3b3c625..a8a3dd5 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -107,6 +107,14 @@ sub build_sugar {
             );
             $into->_add_rule($rule);
         },
+        chain => sub (&) {
+            my $block = shift;
+            my $rule = Path::Dispatcher::Rule::Chain->new(
+                stage => 'on',
+                block => $block,
+            );
+            $into->_add_rule($rule);
+        },
         under => sub {
             my ($matcher, $rules) = @_;
 
@@ -340,7 +348,7 @@ next rule via C<next_rule>
 
 The only argument is a coderef that processes normally (like L<on>).
 
-NOTE: You *can* avoid running a following rule by using C<abort_rule>.
+NOTE: You *can* avoid running a following rule by using C<last_rule>.
 
 An example:
 
diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index b449d66..2f23a30 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -124,6 +124,7 @@ no Any::Moose;
 
 # don't require others to load our subclasses explicitly
 require Path::Dispatcher::Rule::Always;
+require Path::Dispatcher::Rule::Chain;
 require Path::Dispatcher::Rule::CodeRef;
 require Path::Dispatcher::Rule::Dispatch;
 require Path::Dispatcher::Rule::Empty;
diff --git a/lib/Path/Dispatcher/Rule/Chain.pm b/lib/Path/Dispatcher/Rule/Chain.pm
new file mode 100644
index 0000000..762ebcb
--- /dev/null
+++ b/lib/Path/Dispatcher/Rule/Chain.pm
@@ -0,0 +1,29 @@
+package Path::Dispatcher::Rule::Chain;
+use Any::Moose;
+extends 'Path::Dispatcher::Rule';
+
+sub BUILD {
+    my $self = shift;
+
+    if ($self->has_block) {
+        my $block = $self->block;
+        $self->block(sub {
+            $block->(@_);
+            die "Path::Dispatcher next rule\n"; # FIXME From Path::Dispatcher::Declarative... maybe this should go in a common place?
+        });
+    }
+}
+
+sub _match {
+    my $self = shift;
+    my $path = shift;
+    return (1, $path->path);
+}
+
+sub readable_attributes { 'chain' }
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+
+1;
+
diff --git a/lib/Path/Dispatcher/Rule/Under.pm b/lib/Path/Dispatcher/Rule/Under.pm
index c1ebd38..a7dcd4e 100644
--- a/lib/Path/Dispatcher/Rule/Under.pm
+++ b/lib/Path/Dispatcher/Rule/Under.pm
@@ -24,7 +24,17 @@ sub match {
 
     my $new_path = $path->clone_path($prefix_match->leftover);
 
-    return grep { defined } map { $_->match($new_path) } $self->rules;
+    # Pop off @matches until we have a last rule that is not ::Chain
+    #
+    # A better technique than isa might be to use the concept of 'endpoint', 'midpoint', or 'anypoint' rules and
+    # add a method to ::Rule that lets evaluate whether any rule is of the right kind (i.e. ->is_endpoint)
+    #
+    # Because the checking for ::Chain endpointedness is here, this means that outside of an ::Under, ::Chain behaves like
+    # an ::Always (one that will always trigger next_rule if it's block is ran)
+    #
+    return unless my @matches = grep { defined } map { $_->match($new_path) } $self->rules;
+    pop @matches while @matches && $matches[-1]->rule->isa('Path::Dispatcher::Rule::Chain'); 
+    return @matches;
 }
 
 sub readable_attributes { shift->predicate->readable_attributes }
diff --git a/t/020-chain.t b/t/020-chain.t
new file mode 100644
index 0000000..d8f1a1a
--- /dev/null
+++ b/t/020-chain.t
@@ -0,0 +1,169 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+#use Test::More tests => 15;
+use Test::More; plan qw/no_plan/;
+use Path::Dispatcher;
+
+my $predicate = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['ticket'],
+    prefix => 1,
+);
+
+my $chain = Path::Dispatcher::Rule::Chain->new(
+);
+
+my $create = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['create'],
+);
+
+my $update = Path::Dispatcher::Rule::Tokens->new(
+    tokens => ['update'],
+    prefix => 1,
+);
+
+my $under_always = Path::Dispatcher::Rule::Under->new(
+    predicate => $predicate,
+    rules     => [Path::Dispatcher::Rule::Always->new, $create, $update],
+);
+
+my $under_chain = Path::Dispatcher::Rule::Under->new(
+    predicate => $predicate,
+    rules     => [$chain, $create, $update],
+);
+
+my %tests = (
+    "ticket" => {
+        fail => 1,
+        catchall => 1,
+        always => 1,
+    },
+    "ticket create" => {},
+    "ticket update" => {},
+    "  ticket   update  " => {
+        name => "whitespace doesn't matter for token-based rules",
+    },
+    "ticket update foo" => {
+        name => "'ticket update' rule is prefix",
+    },
+
+    "ticket create foo" => {
+        fail => 1,
+        catchall => 1,
+        always => 1,
+        name => "did not match 'ticket create foo' because it's not a suffix",
+    },
+    "comment create" => {
+        fail => 1,
+        name => "did not match 'comment create' because the prefix is ticket",
+    },
+    "ticket delete" => {
+        fail => 1,
+        catchall => 1,
+        always => 1,
+        name => "did not match 'ticket delete' because delete is not a suffix",
+    },
+);
+
+sub run_tests {
+    my $under = shift;
+    my $is_always = shift;
+
+    for my $path (keys %tests) {
+        my $data = $tests{$path};
+        my $name = $data->{name} || $path;
+
+        my $match = $under->match(Path::Dispatcher::Path->new($path));
+        $match = !$match if $data->{fail} && !($is_always && $data->{always}); # Always always matches
+        ok($match, $name);
+    }
+
+    my $catchall = Path::Dispatcher::Rule::Regex->new(
+        regex => qr/()/,
+    );
+
+    $under->add_rule($catchall);
+
+    for my $path (keys %tests) {
+        my $data = $tests{$path};
+        my $name = $data->{name} || $path;
+
+        my $match = $under->match(Path::Dispatcher::Path->new($path));
+        $match = !$match if $data->{fail} && !$data->{catchall};
+        ok($match, $name);
+    }
+}
+
+run_tests $under_chain, 0;
+run_tests $under_always, 1;
+
+my @result;
+
+do {
+    package ChainDispatch;
+    use Path::Dispatcher::Declarative -base;
+
+    under 'ticket' => sub {
+        chain {
+            push @result, "(ticket chain)";
+        };
+        on 'create' => sub { push @result, "ticket create" };
+        chain {
+            push @result, "(ticket chain just for update)";
+    
+        };
+        on 'update' => sub { push @result, "ticket update" };
+    };
+
+    under 'blog' => sub {
+        chain {
+            push @result, "(blog chain)";
+    
+        };
+        under 'post' => sub {
+            chain {
+                push @result, "(after post)";
+        
+            };
+            on 'create' => sub { push @result, "create blog post" };
+            on 'delete' => sub { push @result, "delete blog post" };
+        };
+        chain {
+            push @result, "(before comment)";
+    
+        };
+        under 'comment' => sub {
+            on 'create' => sub { push @result, "create blog comment" };
+            on 'delete' => sub { push @result, "delete blog comment" };
+            chain {
+                push @result, "(never included)";
+        
+            };
+        };
+    };
+};
+
+ChainDispatch->run('ticket create');
+is_deeply([splice @result], ['(ticket chain)', 'ticket create']);
+
+ChainDispatch->run('ticket update');
+is_deeply([splice @result], ['(ticket chain)', '(ticket chain just for update)', 'ticket update']);
+
+ChainDispatch->run('ticket foo');
+is_deeply([splice @result], []);
+
+ChainDispatch->run('blog');
+is_deeply([splice @result], []);
+
+ChainDispatch->run('blog post');
+is_deeply([splice @result], []);
+
+ChainDispatch->run('blog post create');
+is_deeply([splice @result], ['(blog chain)', '(after post)', 'create blog post']);
+
+ChainDispatch->run('blog comment');
+is_deeply([splice @result], []);
+
+ChainDispatch->run('blog comment create');
+is_deeply([splice @result], ['(blog chain)', '(before comment)', 'create blog comment']);
+

commit 3df85e36f351e8093b7ab45f541aac6478da65b8
Author: robertkrimen <robertkrimen at gmail.com>
Date:   Wed Feb 25 17:30:52 2009 -0800

    Created Path::Dispatcher::Builder
    Refactored Path::Dispatcher::Declarative to use ::Builder

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
new file mode 100644
index 0000000..be34489
--- /dev/null
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -0,0 +1,290 @@
+package Path::Dispatcher::Builder;
+
+use strict;
+use warnings;
+
+use Any::Moose;
+
+has dispatcher => (
+    is          => 'ro',
+    isa         => 'Path::Dispatcher',
+    required    => 1,
+    lazy        => 1,
+    default     => sub {
+        return Path::Dispatcher->new
+    },
+);
+
+has case_sensitive_tokens => (
+    is      => 'rw',
+    isa         => 'Bool|CodeRef',
+    default     => 0,
+);
+
+has token_delimiter => (
+    is      => 'rw',
+    isa         => 'Str|CodeRef',
+    default     => ' ',
+);
+
+#sub token_delimiter {
+#    my $self = shift;
+#    my $value = $self->_token_delimiter;
+#    return ref $value eq 'CODE' ? $value->() : $value;
+#}
+## What the magic with coderefs? Because this is based off of ::Declarative, and the caller might not be available at import
+## time (when the sugar is loaded)
+
+#has case_sensitive_tokens => (
+#    reader    => '_case_sensitive_tokens',
+##    is      => 'rw',
+#    isa         => 'Bool|CodeRef',
+#    default     => 0,
+#);
+#sub case_sensitive_tokens {
+#    my $self = shift;
+#    my $value = $self->_case_sensitive_tokens;
+#    return ref $value eq 'CODE' ? $value->() : $value;
+#}
+
+#has token_delimiter => (
+#    reader    => '_token_delimiter',
+##    is      => 'rw',
+#    isa         => 'Str|CodeRef',
+#    default     => ' ',
+#);
+#sub token_delimiter {
+#    my $self = shift;
+#    my $value = $self->_token_delimiter;
+#    return ref $value eq 'CODE' ? $value->() : $value;
+#}
+
+no Any::Moose; # We're gonna use before/after below
+
+our $OUTERMOST_DISPATCHER;
+our $UNDER_RULE;
+
+sub _next_rule () {
+    die "Path::Dispatcher next rule\n";
+}
+
+sub _last_rule () {
+    die "Path::Dispatcher abort\n";
+}
+
+sub dispatch {
+    my $self = shift;
+
+    local $OUTERMOST_DISPATCHER = $self->dispatcher
+        if !$OUTERMOST_DISPATCHER;
+
+    $OUTERMOST_DISPATCHER->dispatch(@_);
+}
+
+sub run {
+    my $self = shift;
+
+    local $OUTERMOST_DISPATCHER = $self->dispatcher
+        if !$OUTERMOST_DISPATCHER;
+
+    $OUTERMOST_DISPATCHER->run(@_);
+}
+
+sub rewrite {
+    my $self = shift;
+    my ($from, $to) = @_;
+    my $rewrite = sub {
+        local $OUTERMOST_DISPATCHER = $self->dispatcher
+            if !$OUTERMOST_DISPATCHER;
+        my $path = ref($to) eq 'CODE' ? $to->() : $to;
+        $OUTERMOST_DISPATCHER->run($path, @_);
+    };
+    $self->_add_rule('on', $from, $rewrite);
+}
+
+sub on {
+    my $self = shift;
+    $self->_add_rule('on', @_);
+}
+
+sub before {
+    my $self = shift;
+    $self->_add_rule('before_on', @_);
+}
+
+sub after {
+    my $self = shift;
+    $self->_add_rule('after_on', @_);
+}
+
+sub then {
+    my $self = shift;
+    my $block = shift;
+    my $rule = Path::Dispatcher::Rule::Always->new(
+        stage => 'on',
+        block => sub {
+            $block->(@_);
+            _next_rule;
+        },
+    );
+    $self->_add_rule($rule);
+}
+
+sub chain {
+    my $self = shift;
+    my $block = shift;
+    my $rule = Path::Dispatcher::Rule::Chain->new(
+        stage => 'on',
+        block => $block,
+    );
+    $self->_add_rule($rule);
+}
+
+sub under {
+    my $self = shift;
+    my ($matcher, $rules) = @_;
+
+    my $predicate = $self->_create_rule('on', $matcher);
+    $predicate->prefix(1);
+
+    my $under = Path::Dispatcher::Rule::Under->new(
+        predicate => $predicate,
+    );
+
+    $self->_add_rule($under, @_);
+
+    do {
+        local $UNDER_RULE = $under;
+        $rules->();
+    };
+}
+
+sub redispatch_to {
+    my $self = shift;
+    my $dispatcher = shift;
+
+    # assume it's a declarative dispatcher
+    if (!ref($dispatcher)) {
+        $dispatcher = $dispatcher->dispatcher;
+    }
+
+    my $redispatch = Path::Dispatcher::Rule::Dispatch->new(
+        dispatcher => $dispatcher,
+    );
+
+    $self->_add_rule($redispatch);
+}
+
+my %rule_creators = (
+    ARRAY => sub {
+        my ($self, $stage, $tokens, $block) = @_;
+        my $case_sensitive = $self->case_sensitive_tokens;
+
+        Path::Dispatcher::Rule::Tokens->new(
+            tokens => $tokens,
+            delimiter => $self->token_delimiter,
+            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
+            $block ? (block => $block) : (),
+        ),
+    },
+    HASH => sub {
+        my ($self, $stage, $metadata_matchers, $block) = @_;
+
+        if (keys %$metadata_matchers == 1) {
+            my ($field) = keys %$metadata_matchers;
+            my ($value) = values %$metadata_matchers;
+            my $matcher = $self->_create_rule($stage, $value);
+
+            return Path::Dispatcher::Rule::Metadata->new(
+                field   => $field,
+                matcher => $matcher,
+                $block ? (block => $block) : (),
+            );
+        }
+
+        die "Doesn't support multiple metadata rules yet";
+    },
+    CODE => sub {
+        my ($self, $stage, $matcher, $block) = @_;
+        Path::Dispatcher::Rule::CodeRef->new(
+            matcher => $matcher,
+            $block ? (block => $block) : (),
+        ),
+    },
+    Regexp => sub {
+        my ($self, $stage, $regex, $block) = @_;
+        Path::Dispatcher::Rule::Regex->new(
+            regex => $regex,
+            $block ? (block => $block) : (),
+        ),
+    },
+    empty => sub {
+        my ($self, $stage, $undef, $block) = @_;
+        Path::Dispatcher::Rule::Empty->new(
+            $block ? (block => $block) : (),
+        ),
+    },
+);
+
+sub _create_rule {
+    my ($self, $stage, $matcher, $block) = @_;
+
+    my $rule_creator;
+
+    if ($matcher eq '') {
+        $rule_creator = $rule_creators{empty};
+    }
+    elsif (!ref($matcher)) {
+        $rule_creator = $rule_creators{ARRAY};
+        $matcher = [$matcher];
+    }
+    else {
+        $rule_creator = $rule_creators{ ref $matcher };
+    }
+
+    $rule_creator or die "I don't know how to create a rule for type $matcher";
+
+    return $rule_creator->($self, $stage, $matcher, $block);
+}
+
+sub _add_rule {
+    my $self = shift;
+    my $rule;
+
+    if (!ref($_[0])) {
+        my ($stage, $matcher, $block) = splice @_, 0, 3;
+        $rule = $self->_create_rule($stage, $matcher, $block);
+    }
+    else {
+        $rule = shift;
+    }
+
+    # FIXME: broken since move from ::Declarative
+    # XXX: caller level should be closer to $Test::Builder::Level
+#    my (undef, $file, $line) = caller(1);
+    my (undef, $file, $line) = caller(2);
+    my $rule_name = "$file:$line";
+
+    if (!defined(wantarray)) {
+        if ($UNDER_RULE) {
+            $UNDER_RULE->add_rule($rule);
+
+            my $full_name = $UNDER_RULE->has_name
+                          ? "(" . $UNDER_RULE->name . " - rule $rule_name)"
+                          : "(anonymous Under - rule $rule_name)";
+
+            $rule->name($full_name);
+        }
+        else {
+            $self->dispatcher->add_rule($rule);
+            $rule->name("(" . $self->dispatcher->name . " - rule $rule_name)");
+        }
+    }
+    else {
+        $rule->name($rule_name);
+        return $rule, @_;
+    }
+}
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index a8a3dd5..3c09df2 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -2,10 +2,97 @@ package Path::Dispatcher::Declarative;
 use strict;
 use warnings;
 use Path::Dispatcher;
+use Path::Dispatcher::Builder;
 
 use Sub::Exporter;
 
 our $CALLER; # Sub::Exporter doesn't make this available
+
+my $exporter = Sub::Exporter::build_exporter({
+    into_level => 1,
+    groups => {
+        default => \&build_sugar,
+    },
+});
+
+*_next_rule = \&Path::Dispatcher::Builder::_next_rule;
+*_last_rule = \&Path::Dispatcher::Builder::_last_rule;
+
+sub token_delimiter { ' ' }
+sub case_sensitive_tokens { undef }
+
+sub import {
+    my $self = shift;
+    my $pkg  = caller;
+
+    my @args = grep { !/^-[bB]ase$/ } @_;
+
+    # just loading the class..
+    return if @args == @_;
+
+    do {
+        no strict 'refs';
+        push @{ $pkg . '::ISA' }, $self;
+    };
+
+    local $CALLER = $pkg;
+
+    $exporter->($self, @args);
+}
+
+sub build_sugar {
+    my ($class, $group, $arg) = @_;
+
+    my $into = $CALLER;
+
+#    my $dispatcher = Path::Dispatcher->new(
+#        name => $into,
+#    );
+#    my $builder = Path::Dispatcher::Builder->new(
+#        token_delimiter => sub { $into->token_delimiter },
+#        case_sensitive_tokens => sub { $into->case_sensitive_tokens },
+#        dispatcher => $dispatcher,
+#    );
+
+    # Why the lazy_builder shenanigans? Because token_delimiter/case_sensitive_tokens subroutines
+    # are probably not ready at import time.
+    my ($builder, $dispatcher);
+    my $lazy_builder = sub {
+        return $builder if $builder;
+        $dispatcher = Path::Dispatcher->new(
+            name => $into,
+        );
+        $builder = Path::Dispatcher::Builder->new(
+            token_delimiter => $into->token_delimiter,
+            case_sensitive_tokens => $into->case_sensitive_tokens,
+            dispatcher => $dispatcher,
+        );
+        return $builder;
+    };
+
+    return {
+        dispatcher      => sub { $lazy_builder->()->dispatcher },
+
+        # NOTE on shift if $into: if caller is $into, then this function is being used as sugar
+        # otherwise, it's probably a method call, so discard the invocant
+        dispatch        => sub { shift if caller ne $into; $lazy_builder->()->dispatch(@_) },
+        run             => sub { shift if caller ne $into; $lazy_builder->()->run(@_) },
+
+        rewrite         => sub { $lazy_builder->()->rewrite(@_) },
+        on              => sub { $lazy_builder->()->on(@_) },
+        before          => sub { $lazy_builder->()->before(@_) },
+        after           => sub { $lazy_builder->()->after(@_) },
+        then            => sub (&) { $lazy_builder->()->then(@_) },
+        chain           => sub (&) { $lazy_builder->()->chain(@_) },
+        under           => sub { $lazy_builder->()->under(@_) },
+        redispatch_to   => sub { $lazy_builder->()->redispatch_to(@_) },
+        next_rule       => \&_next_rule,
+        last_rule       => \&_last_rule,
+    };
+}
+__END__
+
+our $CALLER; # Sub::Exporter doesn't make this available
 our $OUTERMOST_DISPATCHER;
 our $UNDER_RULE;
 

commit a8e0cb12bcebd2d459f8173ee375bee3614f22c5
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 19:38:50 2009 -0500

    Make Chain a subclass of Always

diff --git a/lib/Path/Dispatcher/Rule/Chain.pm b/lib/Path/Dispatcher/Rule/Chain.pm
index 762ebcb..f0b311e 100644
--- a/lib/Path/Dispatcher/Rule/Chain.pm
+++ b/lib/Path/Dispatcher/Rule/Chain.pm
@@ -1,6 +1,6 @@
 package Path::Dispatcher::Rule::Chain;
 use Any::Moose;
-extends 'Path::Dispatcher::Rule';
+extends 'Path::Dispatcher::Rule::Always';
 
 sub BUILD {
     my $self = shift;
@@ -14,12 +14,6 @@ sub BUILD {
     }
 }
 
-sub _match {
-    my $self = shift;
-    my $path = shift;
-    return (1, $path->path);
-}
-
 sub readable_attributes { 'chain' }
 
 __PACKAGE__->meta->make_immutable;

commit ee80012ad54bc9367a8f80a67b344062f227a501
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 19:40:47 2009 -0500

    Always use a plan

diff --git a/t/020-chain.t b/t/020-chain.t
index d8f1a1a..f775401 100644
--- a/t/020-chain.t
+++ b/t/020-chain.t
@@ -1,8 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-#use Test::More tests => 15;
-use Test::More; plan qw/no_plan/;
+use Test::More tests => 40;
 use Path::Dispatcher;
 
 my $predicate = Path::Dispatcher::Rule::Tokens->new(
@@ -10,8 +9,7 @@ my $predicate = Path::Dispatcher::Rule::Tokens->new(
     prefix => 1,
 );
 
-my $chain = Path::Dispatcher::Rule::Chain->new(
-);
+my $chain = Path::Dispatcher::Rule::Chain->new;
 
 my $create = Path::Dispatcher::Rule::Tokens->new(
     tokens => ['create'],
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 779fa17..434cd31 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -2,7 +2,6 @@
 use strict;
 use warnings;
 use Test::More tests => 11;
-#use Test::More plan => qw/no_plan/;
 
 my @calls;
 
diff --git a/t/800-cb-slash-path-delimiter.t b/t/800-cb-slash-path-delimiter.t
index de87290..44bdbb8 100644
--- a/t/800-cb-slash-path-delimiter.t
+++ b/t/800-cb-slash-path-delimiter.t
@@ -1,8 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-#use Test::More tests => 11;
-use Test::More; plan qw/no_plan/;
+use Test::More tests => 3;
 
 my @result;
 
diff --git a/t/801-cb-chaining.t b/t/801-cb-chaining.t
index 11e0d93..415c0df 100644
--- a/t/801-cb-chaining.t
+++ b/t/801-cb-chaining.t
@@ -1,8 +1,7 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-#use Test::More tests => 11;
-use Test::More; plan qw/no_plan/;
+use Test::More tests => 3;
 
 my @result;
 

commit adb2b2565c9c68bd7381f977d59e44ed92906d70
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 19:41:04 2009 -0500

    .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c300104
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+Makefile
+blib/
+pm_to_blib

commit e4e2d6c627c38c90df51d070aaf4b2b178259308
Merge: adb2b25 3df85e3
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 19:49:52 2009 -0500

    Merge branch 'builder' of git://github.com/robertkrimen/path-dispatcher


commit e137f5467e72f14b688c4e4043f621ff271fc670
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 19:55:45 2009 -0500

    Some cleanup in Builder

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index be34489..90397c9 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -1,8 +1,4 @@
 package Path::Dispatcher::Builder;
-
-use strict;
-use warnings;
-
 use Any::Moose;
 
 has dispatcher => (
@@ -10,9 +6,7 @@ has dispatcher => (
     isa         => 'Path::Dispatcher',
     required    => 1,
     lazy        => 1,
-    default     => sub {
-        return Path::Dispatcher->new
-    },
+    default     => sub { return Path::Dispatcher->new },
 );
 
 has case_sensitive_tokens => (
@@ -27,38 +21,6 @@ has token_delimiter => (
     default     => ' ',
 );
 
-#sub token_delimiter {
-#    my $self = shift;
-#    my $value = $self->_token_delimiter;
-#    return ref $value eq 'CODE' ? $value->() : $value;
-#}
-## What the magic with coderefs? Because this is based off of ::Declarative, and the caller might not be available at import
-## time (when the sugar is loaded)
-
-#has case_sensitive_tokens => (
-#    reader    => '_case_sensitive_tokens',
-##    is      => 'rw',
-#    isa         => 'Bool|CodeRef',
-#    default     => 0,
-#);
-#sub case_sensitive_tokens {
-#    my $self = shift;
-#    my $value = $self->_case_sensitive_tokens;
-#    return ref $value eq 'CODE' ? $value->() : $value;
-#}
-
-#has token_delimiter => (
-#    reader    => '_token_delimiter',
-##    is      => 'rw',
-#    isa         => 'Str|CodeRef',
-#    default     => ' ',
-#);
-#sub token_delimiter {
-#    my $self = shift;
-#    my $value = $self->_token_delimiter;
-#    return ref $value eq 'CODE' ? $value->() : $value;
-#}
-
 no Any::Moose; # We're gonna use before/after below
 
 our $OUTERMOST_DISPATCHER;
@@ -285,6 +247,8 @@ sub _add_rule {
         return $rule, @_;
     }
 }
+
 __PACKAGE__->meta->make_immutable;
 
 1;
+

commit 8b8043934d379e31469dc98cd9d51b3302bed497
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 21:16:54 2009 -0500

    Some cleanup

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 90397c9..ba9d7a1 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -4,19 +4,18 @@ use Any::Moose;
 has dispatcher => (
     is          => 'ro',
     isa         => 'Path::Dispatcher',
-    required    => 1,
     lazy        => 1,
     default     => sub { return Path::Dispatcher->new },
 );
 
 has case_sensitive_tokens => (
-    is      => 'rw',
+    is          => 'rw',
     isa         => 'Bool|CodeRef',
     default     => 0,
 );
 
 has token_delimiter => (
-    is      => 'rw',
+    is          => 'rw',
     isa         => 'Str|CodeRef',
     default     => ' ',
 );
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 3c09df2..0f73bea 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -90,262 +90,6 @@ sub build_sugar {
         last_rule       => \&_last_rule,
     };
 }
-__END__
-
-our $CALLER; # Sub::Exporter doesn't make this available
-our $OUTERMOST_DISPATCHER;
-our $UNDER_RULE;
-
-my $exporter = Sub::Exporter::build_exporter({
-    into_level => 1,
-    groups => {
-        default => \&build_sugar,
-    },
-});
-
-sub token_delimiter { ' ' }
-sub case_sensitive_tokens { undef }
-
-sub _next_rule () {
-    die "Path::Dispatcher next rule\n";
-}
-
-sub _last_rule () {
-    die "Path::Dispatcher abort\n";
-}
-
-sub import {
-    my $self = shift;
-    my $pkg  = caller;
-
-    my @args = grep { !/^-[bB]ase$/ } @_;
-
-    # just loading the class..
-    return if @args == @_;
-
-    do {
-        no strict 'refs';
-        push @{ $pkg . '::ISA' }, $self;
-    };
-
-    local $CALLER = $pkg;
-
-    $exporter->($self, @args);
-}
-
-sub build_sugar {
-    my ($class, $group, $arg) = @_;
-
-    my $into = $CALLER;
-
-    my $dispatcher = Path::Dispatcher->new(
-        name => $into,
-    );
-
-    return {
-        dispatcher => sub { $dispatcher },
-        dispatch   => sub {
-            # if caller is $into, then this function is being used as sugar
-            # otherwise, it's probably a method call, so discard the invocant
-            shift if caller ne $into;
-
-            local $OUTERMOST_DISPATCHER = $dispatcher
-                if !$OUTERMOST_DISPATCHER;
-
-            $OUTERMOST_DISPATCHER->dispatch(@_);
-        },
-        run => sub {
-            # if caller is $into, then this function is being used as sugar
-            # otherwise, it's probably a method call, so discard the invocant
-            shift if caller ne $into;
-
-            local $OUTERMOST_DISPATCHER = $dispatcher
-                if !$OUTERMOST_DISPATCHER;
-
-            $OUTERMOST_DISPATCHER->run(@_);
-        },
-        rewrite => sub {
-            my ($from, $to) = @_;
-            my $rewrite = sub {
-                local $OUTERMOST_DISPATCHER = $dispatcher
-                    if !$OUTERMOST_DISPATCHER;
-                my $path = ref($to) eq 'CODE' ? $to->() : $to;
-                $OUTERMOST_DISPATCHER->run($path, @_);
-            };
-            $into->_add_rule('on', $from, $rewrite);
-        },
-        on => sub {
-            $into->_add_rule('on', @_);
-        },
-        before => sub {
-            $into->_add_rule('before_on', @_);
-        },
-        after => sub {
-            $into->_add_rule('after_on', @_);
-        },
-        then => sub (&) {
-            my $block = shift;
-            my $rule = Path::Dispatcher::Rule::Always->new(
-                stage => 'on',
-                block => sub {
-                    $block->(@_);
-                    _next_rule;
-                },
-            );
-            $into->_add_rule($rule);
-        },
-        chain => sub (&) {
-            my $block = shift;
-            my $rule = Path::Dispatcher::Rule::Chain->new(
-                stage => 'on',
-                block => $block,
-            );
-            $into->_add_rule($rule);
-        },
-        under => sub {
-            my ($matcher, $rules) = @_;
-
-            my $predicate = $into->_create_rule('on', $matcher);
-            $predicate->prefix(1);
-
-            my $under = Path::Dispatcher::Rule::Under->new(
-                predicate => $predicate,
-            );
-
-            $into->_add_rule($under, @_);
-
-            do {
-                local $UNDER_RULE = $under;
-                $rules->();
-            };
-        },
-        redispatch_to => sub {
-            my ($dispatcher) = @_;
-
-            # assume it's a declarative dispatcher
-            if (!ref($dispatcher)) {
-                $dispatcher = $dispatcher->dispatcher;
-            }
-
-            my $redispatch = Path::Dispatcher::Rule::Dispatch->new(
-                dispatcher => $dispatcher,
-            );
-
-            $into->_add_rule($redispatch);
-        },
-        next_rule => \&_next_rule,
-        last_rule => \&_last_rule,
-    };
-}
-
-my %rule_creators = (
-    ARRAY => sub {
-        my ($self, $stage, $tokens, $block) = @_;
-        my $case_sensitive = $self->case_sensitive_tokens;
-
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => $tokens,
-            delimiter => $self->token_delimiter,
-            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
-            $block ? (block => $block) : (),
-        ),
-    },
-    HASH => sub {
-        my ($self, $stage, $metadata_matchers, $block) = @_;
-
-        if (keys %$metadata_matchers == 1) {
-            my ($field) = keys %$metadata_matchers;
-            my ($value) = values %$metadata_matchers;
-            my $matcher = $self->_create_rule($stage, $value);
-
-            return Path::Dispatcher::Rule::Metadata->new(
-                field   => $field,
-                matcher => $matcher,
-                $block ? (block => $block) : (),
-            );
-        }
-
-        die "Doesn't support multiple metadata rules yet";
-    },
-    CODE => sub {
-        my ($self, $stage, $matcher, $block) = @_;
-        Path::Dispatcher::Rule::CodeRef->new(
-            matcher => $matcher,
-            $block ? (block => $block) : (),
-        ),
-    },
-    Regexp => sub {
-        my ($self, $stage, $regex, $block) = @_;
-        Path::Dispatcher::Rule::Regex->new(
-            regex => $regex,
-            $block ? (block => $block) : (),
-        ),
-    },
-    empty => sub {
-        my ($self, $stage, $undef, $block) = @_;
-        Path::Dispatcher::Rule::Empty->new(
-            $block ? (block => $block) : (),
-        ),
-    },
-);
-
-sub _create_rule {
-    my ($self, $stage, $matcher, $block) = @_;
-
-    my $rule_creator;
-
-    if ($matcher eq '') {
-        $rule_creator = $rule_creators{empty};
-    }
-    elsif (!ref($matcher)) {
-        $rule_creator = $rule_creators{ARRAY};
-        $matcher = [$matcher];
-    }
-    else {
-        $rule_creator = $rule_creators{ ref $matcher };
-    }
-
-    $rule_creator or die "I don't know how to create a rule for type $matcher";
-
-    return $rule_creator->($self, $stage, $matcher, $block);
-}
-
-sub _add_rule {
-    my $self = shift;
-    my $rule;
-
-    if (!ref($_[0])) {
-        my ($stage, $matcher, $block) = splice @_, 0, 3;
-        $rule = $self->_create_rule($stage, $matcher, $block);
-    }
-    else {
-        $rule = shift;
-    }
-
-    # XXX: caller level should be closer to $Test::Builder::Level
-    my (undef, $file, $line) = caller(1);
-    my $rule_name = "$file:$line";
-
-    if (!defined(wantarray)) {
-        if ($UNDER_RULE) {
-            $UNDER_RULE->add_rule($rule);
-
-            my $full_name = $UNDER_RULE->has_name
-                          ? "(" . $UNDER_RULE->name . " - rule $rule_name)"
-                          : "(anonymous Under - rule $rule_name)";
-
-            $rule->name($full_name);
-        }
-        else {
-            $self->dispatcher->add_rule($rule);
-            $rule->name("(" . $self->dispatcher->name . " - rule $rule_name)");
-        }
-    }
-    else {
-        $rule->name($rule_name);
-        return $rule, @_;
-    }
-}
 
 1;
 

commit 3251b72b1d61dd78998e162e18225b7cb5cb0dd7
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 21:23:50 2009 -0500

    Begin removing ends_dispatch and before/after

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index ba9d7a1..aba953b 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -1,6 +1,9 @@
 package Path::Dispatcher::Builder;
 use Any::Moose;
 
+our $OUTERMOST_DISPATCHER;
+our $UNDER_RULE;
+
 has dispatcher => (
     is          => 'ro',
     isa         => 'Path::Dispatcher',
@@ -20,11 +23,6 @@ has token_delimiter => (
     default     => ' ',
 );
 
-no Any::Moose; # We're gonna use before/after below
-
-our $OUTERMOST_DISPATCHER;
-our $UNDER_RULE;
-
 sub _next_rule () {
     die "Path::Dispatcher next rule\n";
 }
@@ -68,16 +66,6 @@ sub on {
     $self->_add_rule('on', @_);
 }
 
-sub before {
-    my $self = shift;
-    $self->_add_rule('before_on', @_);
-}
-
-sub after {
-    my $self = shift;
-    $self->_add_rule('after_on', @_);
-}
-
 sub then {
     my $self = shift;
     my $block = shift;
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 0f73bea..ae85dcf 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -80,8 +80,6 @@ sub build_sugar {
 
         rewrite         => sub { $lazy_builder->()->rewrite(@_) },
         on              => sub { $lazy_builder->()->on(@_) },
-        before          => sub { $lazy_builder->()->before(@_) },
-        after           => sub { $lazy_builder->()->after(@_) },
         then            => sub (&) { $lazy_builder->()->then(@_) },
         chain           => sub (&) { $lazy_builder->()->chain(@_) },
         under           => sub { $lazy_builder->()->under(@_) },
diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index b425bc0..7e8b6c2 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -42,8 +42,7 @@ sub run {
 
             push @results, scalar $match->run(@args);
 
-            die "Path::Dispatcher abort\n"
-                if $match->ends_dispatch;
+            die "Path::Dispatcher abort\n";
         };
 
         if ($@) {
@@ -103,7 +102,7 @@ matched.
 
 =head2 run
 
-Executes matches until a match's C<ends_dispatch> returns true.
+Executes the first match.
 
 Each match's L<Path::Dispatcher::Match/run> method is evaluated in scalar
 context. The return value of this method is a list of these scalars (or the
diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 5a67b09..f286f80 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -32,14 +32,6 @@ has set_number_vars => (
     default => sub { ref(shift->result) eq 'ARRAY' },
 );
 
-# If we're a before/after (qualified) rule, then yeah, we want to continue
-# dispatching. If we're an "on" (unqualified) rule, then no, you only get one.
-has ends_dispatch => (
-    is      => 'rw',
-    isa     => 'Bool',
-    default => 1,
-);
-
 sub run {
     my $self = shift;
     my @args = @_;
diff --git a/t/004-run.t b/t/004-run.t
index 02558a5..bf01282 100644
--- a/t/004-run.t
+++ b/t/004-run.t
@@ -27,13 +27,9 @@ $result = $dispatcher->run("foobar");
 is($result, "foobar matched");
 
 my $dispatch = $dispatcher->dispatch("foobar");
-for my $match ($dispatch->matches) {
-    $match->ends_dispatch(0);
-}
-
 $result = $dispatch->run("foobar");
 is($result, "foobar matched");
 
 my @results = $dispatch->run("foobar");
-is_deeply(\@results, ["foobar matched", "foo matched"]);
+is_deeply(\@results, ["foobar matched"]);
 
diff --git a/t/102-abort.t b/t/102-abort.t
index 2c2ae15..6674973 100644
--- a/t/102-abort.t
+++ b/t/102-abort.t
@@ -28,12 +28,6 @@ do {
     # this hack is here because "use" expects there to be a file for the module
     BEGIN { MyFramework::Dispatcher->import("-base") }
 
-    before qr/abort/ => sub {
-        push @calls, 'app before abort';
-        last_rule;
-        push @calls, 'app after abort';
-    };
-
     on qr/next rule/ => sub {
         push @calls, 'app before next_rule';
         next_rule;
@@ -51,7 +45,7 @@ do {
 
 MyApp::Dispatcher->run('abort');
 is_deeply([splice @calls], [
-    'app before abort',
+    'framework on abort',
 ]);
 
 MyApp::Dispatcher->run('next rule');

commit 4bddecb585a28ebd883816fc7915f644deb52fc5
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 21:30:12 2009 -0500

    Some more cleanup, killing stages harder

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index aba953b..9bbe836 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -70,7 +70,6 @@ sub then {
     my $self = shift;
     my $block = shift;
     my $rule = Path::Dispatcher::Rule::Always->new(
-        stage => 'on',
         block => sub {
             $block->(@_);
             _next_rule;
@@ -83,7 +82,6 @@ sub chain {
     my $self = shift;
     my $block = shift;
     my $rule = Path::Dispatcher::Rule::Chain->new(
-        stage => 'on',
         block => $block,
     );
     $self->_add_rule($rule);
diff --git a/t/010-return.t b/t/010-return.t
index b87f271..2952d88 100644
--- a/t/010-return.t
+++ b/t/010-return.t
@@ -19,14 +19,12 @@ is_deeply([$dispatcher->run('foo', 42)], ["foo"]);
 my $dispatch = $dispatcher->dispatch('foo');
 is_deeply([$dispatch->run(24)], ["foo"]);
 
-for my $stage (qw/before_on on after_on/) {
-    $dispatcher->add_rule(
-        Path::Dispatcher::Rule::Regex->new(
-            regex => qr/foo/,
-            block => sub { $stage },
-        ),
-    );
-}
+$dispatcher->add_rule(
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr/foo/,
+        block => sub { "jinkies" },
+    ),
+);
 
 is_deeply([$dispatcher->run('foo', 42)], ["foo"]);
 
diff --git a/t/100-declarative.t b/t/100-declarative.t
index 434cd31..3c3ada8 100644
--- a/t/100-declarative.t
+++ b/t/100-declarative.t
@@ -25,15 +25,6 @@ do {
     };
 
     under alpha => sub {
-    #    $Path::Dispatcher::Declarative::UNDER_RULE->add_rule(
-    #        Path::Dispatcher::Rule::Always->new(
-    #            stage => 'on',
-    #            block  => sub {
-    #                print "alpha (chain) ";
-    #                next_rule;
-    #            },
-    #        ),
-    #    );
         then {
             push @calls, "alpha (chain) "; 
         };

commit ea2c83d97d29b2d0fd9ae5e2aa12a8d96f21f4ca
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:02:47 2009 -0500

    Fully remove stages, yay

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 9bbe836..a00f2f3 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -58,12 +58,12 @@ sub rewrite {
         my $path = ref($to) eq 'CODE' ? $to->() : $to;
         $OUTERMOST_DISPATCHER->run($path, @_);
     };
-    $self->_add_rule('on', $from, $rewrite);
+    $self->_add_rule($from, $rewrite);
 }
 
 sub on {
     my $self = shift;
-    $self->_add_rule('on', @_);
+    $self->_add_rule(@_);
 }
 
 sub then {
@@ -91,7 +91,7 @@ sub under {
     my $self = shift;
     my ($matcher, $rules) = @_;
 
-    my $predicate = $self->_create_rule('on', $matcher);
+    my $predicate = $self->_create_rule($matcher);
     $predicate->prefix(1);
 
     my $under = Path::Dispatcher::Rule::Under->new(
@@ -124,7 +124,7 @@ sub redispatch_to {
 
 my %rule_creators = (
     ARRAY => sub {
-        my ($self, $stage, $tokens, $block) = @_;
+        my ($self, $tokens, $block) = @_;
         my $case_sensitive = $self->case_sensitive_tokens;
 
         Path::Dispatcher::Rule::Tokens->new(
@@ -135,12 +135,12 @@ my %rule_creators = (
         ),
     },
     HASH => sub {
-        my ($self, $stage, $metadata_matchers, $block) = @_;
+        my ($self, $metadata_matchers, $block) = @_;
 
         if (keys %$metadata_matchers == 1) {
             my ($field) = keys %$metadata_matchers;
             my ($value) = values %$metadata_matchers;
-            my $matcher = $self->_create_rule($stage, $value);
+            my $matcher = $self->_create_rule($value);
 
             return Path::Dispatcher::Rule::Metadata->new(
                 field   => $field,
@@ -152,21 +152,21 @@ my %rule_creators = (
         die "Doesn't support multiple metadata rules yet";
     },
     CODE => sub {
-        my ($self, $stage, $matcher, $block) = @_;
+        my ($self, $matcher, $block) = @_;
         Path::Dispatcher::Rule::CodeRef->new(
             matcher => $matcher,
             $block ? (block => $block) : (),
         ),
     },
     Regexp => sub {
-        my ($self, $stage, $regex, $block) = @_;
+        my ($self, $regex, $block) = @_;
         Path::Dispatcher::Rule::Regex->new(
             regex => $regex,
             $block ? (block => $block) : (),
         ),
     },
     empty => sub {
-        my ($self, $stage, $undef, $block) = @_;
+        my ($self, $undef, $block) = @_;
         Path::Dispatcher::Rule::Empty->new(
             $block ? (block => $block) : (),
         ),
@@ -174,7 +174,7 @@ my %rule_creators = (
 );
 
 sub _create_rule {
-    my ($self, $stage, $matcher, $block) = @_;
+    my ($self, $matcher, $block) = @_;
 
     my $rule_creator;
 
@@ -191,19 +191,19 @@ sub _create_rule {
 
     $rule_creator or die "I don't know how to create a rule for type $matcher";
 
-    return $rule_creator->($self, $stage, $matcher, $block);
+    return $rule_creator->($self, $matcher, $block);
 }
 
 sub _add_rule {
     my $self = shift;
     my $rule;
 
-    if (!ref($_[0])) {
-        my ($stage, $matcher, $block) = splice @_, 0, 3;
-        $rule = $self->_create_rule($stage, $matcher, $block);
+    if (blessed($_[0]) && $_[0]->isa('Path::Dispatcher::Rule')) {
+        $rule = shift;
     }
     else {
-        $rule = shift;
+        my ($matcher, $block) = splice @_, 0, 2;
+        $rule = $self->_create_rule($matcher, $block);
     }
 
     # FIXME: broken since move from ::Declarative

commit b4b6fb564222823f7f4ecb91c9f414c3b05f3040
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:08:18 2009 -0500

    Use $builder's next_rule and last_rule

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index a00f2f3..7afe38f 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -23,11 +23,11 @@ has token_delimiter => (
     default     => ' ',
 );
 
-sub _next_rule () {
+sub next_rule () {
     die "Path::Dispatcher next rule\n";
 }
 
-sub _last_rule () {
+sub last_rule () {
     die "Path::Dispatcher abort\n";
 }
 
@@ -72,7 +72,7 @@ sub then {
     my $rule = Path::Dispatcher::Rule::Always->new(
         block => sub {
             $block->(@_);
-            _next_rule;
+            next_rule;
         },
     );
     $self->_add_rule($rule);
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index ae85dcf..af490ab 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -15,9 +15,6 @@ my $exporter = Sub::Exporter::build_exporter({
     },
 });
 
-*_next_rule = \&Path::Dispatcher::Builder::_next_rule;
-*_last_rule = \&Path::Dispatcher::Builder::_last_rule;
-
 sub token_delimiter { ' ' }
 sub case_sensitive_tokens { undef }
 
@@ -84,8 +81,8 @@ sub build_sugar {
         chain           => sub (&) { $lazy_builder->()->chain(@_) },
         under           => sub { $lazy_builder->()->under(@_) },
         redispatch_to   => sub { $lazy_builder->()->redispatch_to(@_) },
-        next_rule       => \&_next_rule,
-        last_rule       => \&_last_rule,
+        next_rule       => sub { $lazy_builder->()->next_rule(@_) },
+        last_rule       => sub { $lazy_builder->()->last_rule(@_) },
     };
 }
 

commit c2ed0ef91bf0e3aa996bccb9f21342e3216dda3e
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:11:54 2009 -0500

    Factor out the builder class

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index af490ab..cb3b33c 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -4,6 +4,8 @@ use warnings;
 use Path::Dispatcher;
 use Path::Dispatcher::Builder;
 
+use constant builder_class => 'Path::Dispatcher::Builder';
+
 use Sub::Exporter;
 
 our $CALLER; # Sub::Exporter doesn't make this available
@@ -45,7 +47,7 @@ sub build_sugar {
 #    my $dispatcher = Path::Dispatcher->new(
 #        name => $into,
 #    );
-#    my $builder = Path::Dispatcher::Builder->new(
+#    my $builder = $class->builder_class->new(
 #        token_delimiter => sub { $into->token_delimiter },
 #        case_sensitive_tokens => sub { $into->case_sensitive_tokens },
 #        dispatcher => $dispatcher,
@@ -59,7 +61,7 @@ sub build_sugar {
         $dispatcher = Path::Dispatcher->new(
             name => $into,
         );
-        $builder = Path::Dispatcher::Builder->new(
+        $builder = $class->builder_class->new(
             token_delimiter => $into->token_delimiter,
             case_sensitive_tokens => $into->case_sensitive_tokens,
             dispatcher => $dispatcher,

commit 25095835e93daec688eb790cc52ed8d277e14c6e
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:21:31 2009 -0500

    Factor out dispatcher class

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index cb3b33c..8485692 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -4,6 +4,7 @@ use warnings;
 use Path::Dispatcher;
 use Path::Dispatcher::Builder;
 
+use constant dispatcher_class => 'Path::Dispatcher';
 use constant builder_class => 'Path::Dispatcher::Builder';
 
 use Sub::Exporter;
@@ -44,7 +45,7 @@ sub build_sugar {
 
     my $into = $CALLER;
 
-#    my $dispatcher = Path::Dispatcher->new(
+#    my $dispatcher = $class->dispatcher_class->new(
 #        name => $into,
 #    );
 #    my $builder = $class->builder_class->new(
@@ -58,7 +59,7 @@ sub build_sugar {
     my ($builder, $dispatcher);
     my $lazy_builder = sub {
         return $builder if $builder;
-        $dispatcher = Path::Dispatcher->new(
+        $dispatcher = $class->dispatcher_class->new(
             name => $into,
         );
         $builder = $class->builder_class->new(

commit c993c7890efb0611f77495c8b0aad74777a0cbfd
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:27:46 2009 -0500

    Cleanup; make the options to Path::Dispatcher::Declarative, such as
    token_delimiter, Sub::Exporter options

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 8485692..e4857e9 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -45,47 +45,35 @@ sub build_sugar {
 
     my $into = $CALLER;
 
-#    my $dispatcher = $class->dispatcher_class->new(
-#        name => $into,
-#    );
-#    my $builder = $class->builder_class->new(
-#        token_delimiter => sub { $into->token_delimiter },
-#        case_sensitive_tokens => sub { $into->case_sensitive_tokens },
-#        dispatcher => $dispatcher,
-#    );
-
-    # Why the lazy_builder shenanigans? Because token_delimiter/case_sensitive_tokens subroutines
-    # are probably not ready at import time.
-    my ($builder, $dispatcher);
-    my $lazy_builder = sub {
-        return $builder if $builder;
-        $dispatcher = $class->dispatcher_class->new(
-            name => $into,
-        );
-        $builder = $class->builder_class->new(
-            token_delimiter => $into->token_delimiter,
-            case_sensitive_tokens => $into->case_sensitive_tokens,
-            dispatcher => $dispatcher,
-        );
-        return $builder;
-    };
+    for my $option ('token_delimiter', 'case_sensitive_tokens') {
+        $arg->{$option} = $class->$option
+            if !exists($arg->{$option});
+    }
+
+    my $dispatcher = $class->dispatcher_class->new(name => $into);
+
+    my $builder = $class->builder_class->new(
+        dispatcher => $dispatcher,
+        %$arg,
+    );
 
     return {
-        dispatcher      => sub { $lazy_builder->()->dispatcher },
-
-        # NOTE on shift if $into: if caller is $into, then this function is being used as sugar
-        # otherwise, it's probably a method call, so discard the invocant
-        dispatch        => sub { shift if caller ne $into; $lazy_builder->()->dispatch(@_) },
-        run             => sub { shift if caller ne $into; $lazy_builder->()->run(@_) },
-
-        rewrite         => sub { $lazy_builder->()->rewrite(@_) },
-        on              => sub { $lazy_builder->()->on(@_) },
-        then            => sub (&) { $lazy_builder->()->then(@_) },
-        chain           => sub (&) { $lazy_builder->()->chain(@_) },
-        under           => sub { $lazy_builder->()->under(@_) },
-        redispatch_to   => sub { $lazy_builder->()->redispatch_to(@_) },
-        next_rule       => sub { $lazy_builder->()->next_rule(@_) },
-        last_rule       => sub { $lazy_builder->()->last_rule(@_) },
+        dispatcher    => sub { $builder->dispatcher },
+        rewrite       => sub { $builder->rewrite(@_) },
+        on            => sub { $builder->on(@_) },
+        under         => sub { $builder->under(@_) },
+        redispatch_to => sub { $builder->redispatch_to(@_) },
+        next_rule     => sub { $builder->next_rule(@_) },
+        last_rule     => sub { $builder->last_rule(@_) },
+
+        then  => sub (&) { $builder->then(@_) },
+        chain => sub (&) { $builder->chain(@_) },
+
+        # NOTE on shift if $into: if caller is $into, then this function is
+        # being used as sugar otherwise, it's probably a method call, so
+        # discard the invocant
+        dispatch => sub { shift if caller ne $into; $builder->dispatch(@_) },
+        run      => sub { shift if caller ne $into; $builder->run(@_) },
     };
 }
 
diff --git a/t/104-config.t b/t/104-config.t
index d0d28f4..e72e57b 100644
--- a/t/104-config.t
+++ b/t/104-config.t
@@ -7,10 +7,10 @@ my @calls;
 
 do {
     package RESTy::Dispatcher;
-    use Path::Dispatcher::Declarative -base;
-
-    sub token_delimiter { '/' }
-    sub case_sensitive_tokens { 0 }
+    use Path::Dispatcher::Declarative -base, -default => {
+        token_delimiter => '/',
+        case_sensitive_tokens => 0,
+    };
 
     on ['=', 'model', 'Comment'] => sub { push @calls, $3 };
 };
diff --git a/t/800-cb-slash-path-delimiter.t b/t/800-cb-slash-path-delimiter.t
index 44bdbb8..f91f35b 100644
--- a/t/800-cb-slash-path-delimiter.t
+++ b/t/800-cb-slash-path-delimiter.t
@@ -7,9 +7,9 @@ my @result;
 
 do {
     package MyDispatcher;
-    use Path::Dispatcher::Declarative -base;
-
-    sub token_delimiter { '/' }
+    use Path::Dispatcher::Declarative -base, -default => {
+        token_delimiter => '/',
+    };
 
     under show => sub {
         on inventory => sub {

commit 54969918119ff65f78f92cc868a657e072d19a56
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:29:19 2009 -0500

    More simplifications

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 7afe38f..2eb563a 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -13,13 +13,13 @@ has dispatcher => (
 
 has case_sensitive_tokens => (
     is          => 'rw',
-    isa         => 'Bool|CodeRef',
+    isa         => 'Bool',
     default     => 0,
 );
 
 has token_delimiter => (
     is          => 'rw',
-    isa         => 'Str|CodeRef',
+    isa         => 'Str',
     default     => ' ',
 );
 
@@ -125,12 +125,11 @@ sub redispatch_to {
 my %rule_creators = (
     ARRAY => sub {
         my ($self, $tokens, $block) = @_;
-        my $case_sensitive = $self->case_sensitive_tokens;
 
         Path::Dispatcher::Rule::Tokens->new(
             tokens => $tokens,
             delimiter => $self->token_delimiter,
-            defined $case_sensitive ? (case_sensitive => $case_sensitive) : (),
+            case_sensitive => $self->case_sensitive_tokens,
             $block ? (block => $block) : (),
         ),
     },
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index e4857e9..7539a72 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -3,12 +3,11 @@ use strict;
 use warnings;
 use Path::Dispatcher;
 use Path::Dispatcher::Builder;
+use Sub::Exporter;
 
 use constant dispatcher_class => 'Path::Dispatcher';
 use constant builder_class => 'Path::Dispatcher::Builder';
 
-use Sub::Exporter;
-
 our $CALLER; # Sub::Exporter doesn't make this available
 
 my $exporter = Sub::Exporter::build_exporter({

commit f9a835332952f251553f827a07b55643caa65003
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:35:06 2009 -0500

    Restore the default of case sensitive tokens

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 2eb563a..63f9eaf 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -14,7 +14,7 @@ has dispatcher => (
 has case_sensitive_tokens => (
     is          => 'rw',
     isa         => 'Bool',
-    default     => 0,
+    default     => 1,
 );
 
 has token_delimiter => (
diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 7539a72..4131bff 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -45,8 +45,12 @@ sub build_sugar {
     my $into = $CALLER;
 
     for my $option ('token_delimiter', 'case_sensitive_tokens') {
-        $arg->{$option} = $class->$option
-            if !exists($arg->{$option});
+        next if exists $arg->{$option};
+
+        my $default = $class->$option;
+        next unless defined $default; # use the builder's default
+
+        $arg->{$option} = $class->$option;
     }
 
     my $dispatcher = $class->dispatcher_class->new(name => $into);

commit 14b2e4449b86cbb2519336832b0e6b42e3c13d04
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:41:03 2009 -0500

    Factor out populating defaults

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index 4131bff..ee7564c 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -24,7 +24,7 @@ sub import {
     my $self = shift;
     my $pkg  = caller;
 
-    my @args = grep { !/^-[bB]ase$/ } @_;
+    my @args = grep { !/^-base$/i } @_;
 
     # just loading the class..
     return if @args == @_;
@@ -44,14 +44,7 @@ sub build_sugar {
 
     my $into = $CALLER;
 
-    for my $option ('token_delimiter', 'case_sensitive_tokens') {
-        next if exists $arg->{$option};
-
-        my $default = $class->$option;
-        next unless defined $default; # use the builder's default
-
-        $arg->{$option} = $class->$option;
-    }
+    $class->populate_defaults($arg);
 
     my $dispatcher = $class->dispatcher_class->new(name => $into);
 
@@ -80,6 +73,21 @@ sub build_sugar {
     };
 }
 
+sub populate_defaults {
+    my $class = shift;
+    my $arg  = shift;
+
+    for my $option ('token_delimiter', 'case_sensitive_tokens') {
+        next if exists $arg->{$option};
+
+        my $default = $class->$option;
+        next unless defined $default; # use the builder's default
+
+        $arg->{$option} = $class->$option;
+    }
+}
+
+
 1;
 
 __END__

commit 44392e6e8ab20a287ef4c917eb145da302720879
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:43:48 2009 -0500

    Make rule_creators a method

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 63f9eaf..1f7798b 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -122,55 +122,57 @@ sub redispatch_to {
     $self->_add_rule($redispatch);
 }
 
-my %rule_creators = (
-    ARRAY => sub {
-        my ($self, $tokens, $block) = @_;
-
-        Path::Dispatcher::Rule::Tokens->new(
-            tokens => $tokens,
-            delimiter => $self->token_delimiter,
-            case_sensitive => $self->case_sensitive_tokens,
-            $block ? (block => $block) : (),
-        ),
-    },
-    HASH => sub {
-        my ($self, $metadata_matchers, $block) = @_;
-
-        if (keys %$metadata_matchers == 1) {
-            my ($field) = keys %$metadata_matchers;
-            my ($value) = values %$metadata_matchers;
-            my $matcher = $self->_create_rule($value);
-
-            return Path::Dispatcher::Rule::Metadata->new(
-                field   => $field,
+sub rule_creators {
+    return {
+        ARRAY => sub {
+            my ($self, $tokens, $block) = @_;
+
+            Path::Dispatcher::Rule::Tokens->new(
+                tokens => $tokens,
+                delimiter => $self->token_delimiter,
+                case_sensitive => $self->case_sensitive_tokens,
+                $block ? (block => $block) : (),
+            ),
+        },
+        HASH => sub {
+            my ($self, $metadata_matchers, $block) = @_;
+
+            if (keys %$metadata_matchers == 1) {
+                my ($field) = keys %$metadata_matchers;
+                my ($value) = values %$metadata_matchers;
+                my $matcher = $self->_create_rule($value);
+
+                return Path::Dispatcher::Rule::Metadata->new(
+                    field   => $field,
+                    matcher => $matcher,
+                    $block ? (block => $block) : (),
+                );
+            }
+
+            die "Doesn't support multiple metadata rules yet";
+        },
+        CODE => sub {
+            my ($self, $matcher, $block) = @_;
+            Path::Dispatcher::Rule::CodeRef->new(
                 matcher => $matcher,
                 $block ? (block => $block) : (),
-            );
-        }
-
-        die "Doesn't support multiple metadata rules yet";
-    },
-    CODE => sub {
-        my ($self, $matcher, $block) = @_;
-        Path::Dispatcher::Rule::CodeRef->new(
-            matcher => $matcher,
-            $block ? (block => $block) : (),
-        ),
-    },
-    Regexp => sub {
-        my ($self, $regex, $block) = @_;
-        Path::Dispatcher::Rule::Regex->new(
-            regex => $regex,
-            $block ? (block => $block) : (),
-        ),
-    },
-    empty => sub {
-        my ($self, $undef, $block) = @_;
-        Path::Dispatcher::Rule::Empty->new(
-            $block ? (block => $block) : (),
-        ),
-    },
-);
+            ),
+        },
+        Regexp => sub {
+            my ($self, $regex, $block) = @_;
+            Path::Dispatcher::Rule::Regex->new(
+                regex => $regex,
+                $block ? (block => $block) : (),
+            ),
+        },
+        empty => sub {
+            my ($self, $undef, $block) = @_;
+            Path::Dispatcher::Rule::Empty->new(
+                $block ? (block => $block) : (),
+            ),
+        },
+    };
+}
 
 sub _create_rule {
     my ($self, $matcher, $block) = @_;
@@ -178,14 +180,14 @@ sub _create_rule {
     my $rule_creator;
 
     if ($matcher eq '') {
-        $rule_creator = $rule_creators{empty};
+        $rule_creator = $self->rule_creators->{empty};
     }
     elsif (!ref($matcher)) {
-        $rule_creator = $rule_creators{ARRAY};
+        $rule_creator = $self->rule_creators->{ARRAY};
         $matcher = [$matcher];
     }
     else {
-        $rule_creator = $rule_creators{ ref $matcher };
+        $rule_creator = $self->rule_creators->{ ref $matcher };
     }
 
     $rule_creator or die "I don't know how to create a rule for type $matcher";

commit 5a897c87fc6c35e88c8de88175eaec807d55eb16
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:44:39 2009 -0500

    Unimport Any::Moose's sugar

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 1f7798b..531b23f 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -235,6 +235,7 @@ sub _add_rule {
 }
 
 __PACKAGE__->meta->make_immutable;
+no Any::Moose;
 
 1;
 

commit 9e77dcd44efec02fe84b15781cc9f77cd2b73d72
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:45:27 2009 -0500

    Ignore more dist crap

diff --git a/.gitignore b/.gitignore
index c300104..55abb91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,9 @@
+META.yml
 Makefile
 blib/
+inc/
+*.sw[po]
 pm_to_blib
+MANIFEST
+MANIFEST.bak
+SIGNATURE

commit 78b0b10849c2ee077c0ba734314238fccac64389
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:49:13 2009 -0500

    Don't stomp on any rule's existing name

diff --git a/lib/Path/Dispatcher/Builder.pm b/lib/Path/Dispatcher/Builder.pm
index 531b23f..6821ef5 100644
--- a/lib/Path/Dispatcher/Builder.pm
+++ b/lib/Path/Dispatcher/Builder.pm
@@ -221,15 +221,17 @@ sub _add_rule {
                           ? "(" . $UNDER_RULE->name . " - rule $rule_name)"
                           : "(anonymous Under - rule $rule_name)";
 
-            $rule->name($full_name);
+            $rule->name($full_name) unless $rule->has_name;
         }
         else {
             $self->dispatcher->add_rule($rule);
-            $rule->name("(" . $self->dispatcher->name . " - rule $rule_name)");
+            $rule->name("(" . $self->dispatcher->name . " - rule $rule_name)")
+                unless $rule->has_name;
         }
     }
     else {
-        $rule->name($rule_name);
+        $rule->name($rule_name)
+            unless $rule->has_name;
         return $rule, @_;
     }
 }

commit 5842379ea3010b6deb831079201809ccd597aeee
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 22:59:54 2009 -0500

    Bring Cookbook up to date, add tests to make sure I'm not lying

diff --git a/lib/Path/Dispatcher/Cookbook.pod b/lib/Path/Dispatcher/Cookbook.pod
index 65f04e2..f3fbcfd 100644
--- a/lib/Path/Dispatcher/Cookbook.pod
+++ b/lib/Path/Dispatcher/Cookbook.pod
@@ -8,22 +8,35 @@ Path::Dispatcher::Cookbook - A cookbook for Path::Dispatcher
 
 =head2 How can I change the path delimiter from a space ' ' to a slash '/'?
 
-In your Dispatcher object, define the C<token_delimiter> subroutine to return a slash '/':
+When importing the L<Path::Dispatcher::Declarative> sugar, specify the
+C<token_delimiter> option for the C<default> group.
 
-    package MyDispatcher;
-    use Path::Dispatcher::Declarative -base;
+    package My::Dispatcher;
+    use Path::Dispatcher::Declarative -base, -default => {
+        token_delimiter => '/',
+    };
 
-    sub token_delimiter { '/' } # Or whatever delimiter you want to use
+Or define a subclass of L<Path::Dispatcher::Declarative> with a
+C<token_delimiter> method:
+
+    package Web::Dispatcher;
+    use base 'Path::Dispatcher::Declarative';
+    
+    use constant token_delimiter => '/';
+    
+    
+    package My::Other::Dispatcher;
+    use Web::Dispatcher -base;
 
 =head2 How can I do rule chaining (like in Catalyst)?
 
-You can use a C<then> rule approximate chaining behavior:
+You can use a C<chain> rule approximate chaining behavior:
 
     package MyDispatcher;
     use Path::Dispatcher::Declarative -base;
 
     under show => sub {
-        then {
+        chain {
             print "Displaying ";
         };
         on inventory => sub {
@@ -43,3 +56,4 @@ You can use a C<then> rule approximate chaining behavior:
     MyDispatcher->run("display score"); # "Displaying score\n ..."
 
 =cut
+
diff --git a/t/021-declarative-defaults.t b/t/021-declarative-defaults.t
new file mode 100644
index 0000000..2422f63
--- /dev/null
+++ b/t/021-declarative-defaults.t
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+my @calls;
+do {
+    package Web::Dispatcher;
+    use base 'Path::Dispatcher::Declarative';
+
+    use constant token_delimiter => '/';
+
+
+    package My::Other::Dispatcher;
+    # we can't use a package in the same file :/
+    BEGIN { Web::Dispatcher->import('-base') };
+
+    on ['foo', 'bar'] => sub {
+        push @calls, '/foo/bar';
+    };
+};
+
+My::Other::Dispatcher->run('/foo/bar');
+is_deeply([splice @calls], ['/foo/bar']);
+

commit 836bc37b35a914279de5441e4a8af05713ddcc8b
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 23:19:26 2009 -0500

    Be way more lenient in boxing a path - anything that isn't a
    Path::Dispatcher::Path is boxed

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 7b59b1c..6631ab6 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -28,8 +28,8 @@ sub dispatch {
     my $self = shift;
     my $path = shift;
 
-    # Automatically box string paths
-    if (!ref($path)) {
+    # Automatically box paths
+    unless (blessed($path) && $path->isa('Path::Dispatcher::Rule')) {
         $path = $self->path_class->new(
             path => $path,
         );

commit 6ec9f46066d35cf41e34229c32fa6d9daf8c47b8
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 23:30:53 2009 -0500

    Oops, wrong class name

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 6631ab6..7995a60 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -29,7 +29,7 @@ sub dispatch {
     my $path = shift;
 
     # Automatically box paths
-    unless (blessed($path) && $path->isa('Path::Dispatcher::Rule')) {
+    unless (blessed($path) && $path->isa('Path::Dispatcher::Path')) {
         $path = $self->path_class->new(
             path => $path,
         );

commit 676f77fc387bf5f570a9efecebedb02214954da9
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 23:35:44 2009 -0500

    Don't specify defaults more than needed

diff --git a/lib/Path/Dispatcher/Declarative.pm b/lib/Path/Dispatcher/Declarative.pm
index ee7564c..eda3f31 100644
--- a/lib/Path/Dispatcher/Declarative.pm
+++ b/lib/Path/Dispatcher/Declarative.pm
@@ -17,9 +17,6 @@ my $exporter = Sub::Exporter::build_exporter({
     },
 });
 
-sub token_delimiter { ' ' }
-sub case_sensitive_tokens { undef }
-
 sub import {
     my $self = shift;
     my $pkg  = caller;
@@ -79,6 +76,7 @@ sub populate_defaults {
 
     for my $option ('token_delimiter', 'case_sensitive_tokens') {
         next if exists $arg->{$option};
+        next unless $class->can($option);
 
         my $default = $class->$option;
         next unless defined $default; # use the builder's default

commit a9adcc66c132788bf709fb7f064e1b3c7b0de284
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 23:40:48 2009 -0500

    0.10 changes

diff --git a/Changes b/Changes
index 20c29a6..f2241d3 100644
--- a/Changes
+++ b/Changes
@@ -1,9 +1,25 @@
 Revision history for Path-Dispatcher
 
-0.10
-        Added slash-path-delimeter recipe
-        Added chaining recipe
-        (tenative) Added 'then' sugar to ::Declarative
+0.10 Fri Mar 6 23:40:42 2009
+        The way you specify token_delimiter and case_sensitive_tokens has
+            changed! You now say:
+                use Path::Dispatcher::Declarative -base, -defaults => {
+                    token_delimiter       => '/',
+                    case_sensitive_tokens => 0,
+                };
+
+        Added Path::Dispatcher::Cookbook (grink)
+
+        Added Path::Dispatcher::Builder which now backs
+            Path::Dispatcher::Declarative (grink)
+
+        then {} rule which is an "always" with that uses next_rule (grink)
+
+        chain {} rule which is like Catalyst's chain (grink)
+
+        Remove the last vestiges of stages (Sartak)
+
+        Many minor fixes (Sartak)
 
 0.09 Mon Feb 9 21:12:18 2009
         Avoid using method modifiers since it's potentially another dep.

commit f804e3ce5a5b9c3eb0efd0374a1989336de1e361
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Mar 6 23:43:56 2009 -0500

    Bump to 0.11

diff --git a/Changes b/Changes
index f2241d3..d522c97 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.11
+
 0.10 Fri Mar 6 23:40:42 2009
         The way you specify token_delimiter and case_sensitive_tokens has
             changed! You now say:
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 7995a60..5970da7 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,7 @@
 package Path::Dispatcher;
 use Any::Moose;
 
-our $VERSION = '0.10';
+our $VERSION = '0.11';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit ee8bbf3b597de4047e79304b7614fd5570d6e75c
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sat Mar 7 16:49:40 2009 -0500

    Consistency fixes from confound

diff --git a/lib/Path/Dispatcher/Cookbook.pod b/lib/Path/Dispatcher/Cookbook.pod
index f3fbcfd..d26db50 100644
--- a/lib/Path/Dispatcher/Cookbook.pod
+++ b/lib/Path/Dispatcher/Cookbook.pod
@@ -51,9 +51,9 @@ You can use a C<chain> rule approximate chaining behavior:
 
     package main;
 
-    MyDispatcher->run("display inventory"); # "Displaying inventory\n ..."
+    MyDispatcher->run("show inventory"); # "Displaying inventory:\n ..."
 
-    MyDispatcher->run("display score"); # "Displaying score\n ..."
+    MyDispatcher->run("show score"); # "Displaying score:\n ..."
 
 =cut
 

commit 139adf273504585e6299b3722cb43c3d61d806e9
Author: Shawn M Moore <sartak at gmail.com>
Date:   Thu Mar 26 21:17:05 2009 -0400

    Better example, since you should always run coderefs through $match->run

diff --git a/lib/Path/Dispatcher/Rule.pm b/lib/Path/Dispatcher/Rule.pm
index 2f23a30..dc8e3a4 100644
--- a/lib/Path/Dispatcher/Rule.pm
+++ b/lib/Path/Dispatcher/Rule.pm
@@ -154,7 +154,7 @@ Path::Dispatcher::Rule - predicate and codeblock
 
     my $match = $rule->match("quit"); # creates a Path::Dispatcher::Match
 
-    $rule->run; # exits the program
+    $match->run; # exits the program
 
 =head1 DESCRIPTION
 

commit c70903c7f386ce44906d9e85b2298d8b60963a58
Author: Shawn M Moore <sartak at gmail.com>
Date:   Thu Mar 26 21:25:47 2009 -0400

    The attribute name is _matches not matches

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 7e8b6c2..7fac5b7 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -19,9 +19,9 @@ sub add_match {
     push @{ $self->{matches} }, @_;
 }
 
-sub matches     { @{ shift->{matches} } }
-sub has_match   { scalar @{ shift->{matches} } }
-sub first_match { shift->{matches}[0] }
+sub matches     { @{ shift->{_matches} } }
+sub has_match   { scalar @{ shift->{_matches} } }
+sub first_match { shift->{_matches}[0] }
 
 # aliases
 __PACKAGE__->meta->add_method(add_matches => __PACKAGE__->can('add_match'));

commit bab45d108e52afaf2685a1bef384885e2e3e7c6e
Author: Shawn M Moore <sartak at gmail.com>
Date:   Thu Mar 26 22:00:56 2009 -0400

    Another badly named key

diff --git a/lib/Path/Dispatcher/Dispatch.pm b/lib/Path/Dispatcher/Dispatch.pm
index 7fac5b7..62fa29b 100644
--- a/lib/Path/Dispatcher/Dispatch.pm
+++ b/lib/Path/Dispatcher/Dispatch.pm
@@ -16,7 +16,7 @@ sub add_match {
         or confess "$_ is not a Path::Dispatcher::Match"
             for @_;
 
-    push @{ $self->{matches} }, @_;
+    push @{ $self->{_matches} }, @_;
 }
 
 sub matches     { @{ shift->{_matches} } }

commit 11b3a44f0665a21642cf55a4c35651b785f93f68
Author: Shawn M Moore <sartak at gmail.com>
Date:   Wed Apr 15 00:15:08 2009 -0400

    clone_object was deprecated

diff --git a/lib/Path/Dispatcher/Path.pm b/lib/Path/Dispatcher/Path.pm
index 36215e2..c5b04c7 100644
--- a/lib/Path/Dispatcher/Path.pm
+++ b/lib/Path/Dispatcher/Path.pm
@@ -30,7 +30,7 @@ sub clone_path {
     my $self = shift;
     my $path = shift;
 
-    return $self->meta->clone_instance($self, path => $path, @_);
+    return $self->meta->clone_object($self, path => $path, @_);
 }
 
 sub get_metadata {

commit 17648c5b4d0d67361a302a3289b845c41b7cfd84
Author: Shawn M Moore <sartak at gmail.com>
Date:   Wed Apr 15 00:15:21 2009 -0400

    Ignore cover_db

diff --git a/.gitignore b/.gitignore
index 55abb91..fa7c8d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ pm_to_blib
 MANIFEST
 MANIFEST.bak
 SIGNATURE
+cover_db/

commit 52153893eca6dfe94ae218b8e5fba08ec2aadf0b
Author: Shawn M Moore <sartak at gmail.com>
Date:   Wed Apr 15 02:47:54 2009 -0400

    0.11 Changes

diff --git a/Changes b/Changes
index d522c97..130d66d 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,11 @@
 Revision history for Path-Dispatcher
 
-0.11
+0.11 Wed Apr 15 02:47:50 2009
+        Fix some misnamed keys caused by de-AttributeHelper-ing
+
+        Stop using some deprecated Moose features
+
+        Doc fixes (Sartak and confound)
 
 0.10 Fri Mar 6 23:40:42 2009
         The way you specify token_delimiter and case_sensitive_tokens has

commit 64711ae4642ecf756224efa1f65cbb7818c6f4a8
Author: Shawn M Moore <sartak at gmail.com>
Date:   Wed Apr 15 02:49:30 2009 -0400

    Bump to 0.12

diff --git a/Changes b/Changes
index 130d66d..9ca6c46 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.12
+
 0.11 Wed Apr 15 02:47:50 2009
         Fix some misnamed keys caused by de-AttributeHelper-ing
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 5970da7..33a7b82 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,7 @@
 package Path::Dispatcher;
 use Any::Moose;
 
-our $VERSION = '0.11';
+our $VERSION = '0.12';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit f820221abc60f34842bb6d88d9c3ea0c66ed5062
Author: Shawn M Moore <sartak at gmail.com>
Date:   Thu Apr 16 21:57:24 2009 -0400

    Give dispatch rule a "rules" method

diff --git a/lib/Path/Dispatcher/Rule/Dispatch.pm b/lib/Path/Dispatcher/Rule/Dispatch.pm
index 5e2e523..16418c1 100644
--- a/lib/Path/Dispatcher/Rule/Dispatch.pm
+++ b/lib/Path/Dispatcher/Rule/Dispatch.pm
@@ -6,6 +6,7 @@ has dispatcher => (
     is       => 'rw',
     isa      => 'Path::Dispatcher',
     required => 1,
+    handles  => ['rules'],
 );
 
 sub match {

commit 2b4ed8571a0a331378b603b659cb749974672dbd
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Apr 17 03:16:18 2009 -0400

    Failing test for not preserving undef in run_with_number_vars

diff --git a/t/022-numbers-undef.t b/t/022-numbers-undef.t
new file mode 100644
index 0000000..d4eff58
--- /dev/null
+++ b/t/022-numbers-undef.t
@@ -0,0 +1,20 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 2;
+use Path::Dispatcher;
+
+my @recaptures;
+my $rule = Path::Dispatcher::Rule::Regex->new(
+    regex => qr/^(foo)(bar)?(baz)$/,
+    block => sub {
+        push @recaptures, $1, $2, $3;
+    },
+);
+
+my $match = $rule->match(Path::Dispatcher::Path->new("foobaz"));
+is_deeply($match->result, ['foo', undef, 'baz']);
+
+$match->run;
+is_deeply(\@recaptures, ['foo', undef, 'baz']);
+

commit f781f723f3f576eff81fab5feda575072ef5e939
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Apr 17 03:19:17 2009 -0400

    Handle undef number vars better

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index f286f80..a07743d 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -55,8 +55,8 @@ sub run_with_number_vars {
     # we don't have direct write access to $1 and friends, so we have to
     # do this little hack. the only way we can update $1 is by matching
     # against a regex (5.10 fixes that)..
-    my $re  = join '', map { "(\Q$_\E)" } @_;
-    my $str = join '',                    @_;
+    my $re  = join '', map { defined($_) ? "(\Q$_\E)" : "(wontmatch)?" } @_;
+    my $str = join '', map { defined($_) ? $_         : ""             } @_;
 
     # we need to check length because Perl's annoying gotcha of the empty regex
     # actually being an alias for whatever the previously used regex was 

commit c5ff0953b0e55358b9259bd90c31cb2f7475c4ae
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Apr 17 03:19:25 2009 -0400

    Anchor the regex for efficiency

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index a07743d..5ba67b9 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -64,7 +64,7 @@ sub run_with_number_vars {
     # we need to do the match anyway, because we have to clear the number vars
     ($str, $re) = ("x", "x") if length($str) == 0;
 
-    $str =~ $re
+    $str =~ qr{^$re$}
         or die "Unable to match '$str' against a copy of itself ($re)!";
 
     $code->();

commit 1e09a1605d83dfb99496ca702eb2aae04498613f
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Apr 17 03:21:09 2009 -0400

    0.12 changes

diff --git a/Changes b/Changes
index 9ca6c46..d17b4a2 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,10 @@
 Revision history for Path-Dispatcher
 
-0.12
+0.12 Fri Apr 17 03:21:05 2009
+        Fix a bug with undefined capture variables being converted to the
+            empty string and throwing warnings (reported by obra)
+
+        Give Path::Dispatcher::Rule::Dispatch a "rules" method
 
 0.11 Wed Apr 15 02:47:50 2009
         Fix some misnamed keys caused by de-AttributeHelper-ing

commit 592676de411feb24c3ce4ec4edd764da9badb541
Author: Shawn M Moore <sartak at gmail.com>
Date:   Fri Apr 17 03:30:56 2009 -0400

    Bump to 0.13

diff --git a/Changes b/Changes
index d17b4a2..6619eb5 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.13
+
 0.12 Fri Apr 17 03:21:05 2009
         Fix a bug with undefined capture variables being converted to the
             empty string and throwing warnings (reported by obra)
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 33a7b82..94a2ee3 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,7 +1,7 @@
 package Path::Dispatcher;
 use Any::Moose;
 
-our $VERSION = '0.12';
+our $VERSION = '0.13';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit 6bcaf42958820eeac12ea19e01270959b8e22c95
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 04:45:12 2009 -0400

    Force 0 or 1 for the set_number_vars default

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 5ba67b9..2414195 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -29,7 +29,7 @@ has set_number_vars => (
     is      => 'rw',
     isa     => 'Bool',
     lazy    => 1,
-    default => sub { ref(shift->result) eq 'ARRAY' },
+    default => sub { ref(shift->result) eq 'ARRAY' ? 0 : 1 },
 );
 
 sub run {

commit 7f8763f152cb27937f2e572877d261963f562279
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 04:50:30 2009 -0400

    I am a big dummy

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index 2414195..ce3151e 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -29,7 +29,7 @@ has set_number_vars => (
     is      => 'rw',
     isa     => 'Bool',
     lazy    => 1,
-    default => sub { ref(shift->result) eq 'ARRAY' ? 0 : 1 },
+    default => sub { ref(shift->result) eq 'ARRAY' ? 1 : 0 },
 );
 
 sub run {

commit cd15abb1a574e312685861d1b2a5f5180f62dd03
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:25:47 2009 -0400

    Add unshift_rule method

diff --git a/lib/Path/Dispatcher/Role/Rules.pm b/lib/Path/Dispatcher/Role/Rules.pm
index 8e6ba1b..f05d3e6 100644
--- a/lib/Path/Dispatcher/Role/Rules.pm
+++ b/lib/Path/Dispatcher/Role/Rules.pm
@@ -18,6 +18,16 @@ sub add_rule {
     push @{ $self->{_rules} }, @_;
 }
 
+sub unshift_rule {
+    my $self = shift;
+
+    $_->isa('Path::Dispatcher::Rule')
+        or confess "$_ is not a Path::Dispatcher::Rule"
+            for @_;
+
+    unshift @{ $self->{_rules} }, @_;
+}
+
 sub rules { @{ shift->{_rules} } }
 
 no Any::Moose;

commit 4ab67a2ed5a0aa12b6a1fc6c5684145f01650bfc
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:38:25 2009 -0400

    0.13 changes

diff --git a/Changes b/Changes
index 6619eb5..d09b268 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 Revision history for Path-Dispatcher
 
-0.13
+0.13 Sun Aug 9 13:38:19 2009
+        Add unshift_rule to classes that do Role::Rules
 
 0.12 Fri Apr 17 03:21:05 2009
         Fix a bug with undefined capture variables being converted to the

commit d93ee06ca6a157fa04d98d9a8a7549cc50a8f45f
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:39:03 2009 -0400

    Revert ? 1 : 0, it was just a weird symptom

diff --git a/lib/Path/Dispatcher/Match.pm b/lib/Path/Dispatcher/Match.pm
index ce3151e..5ba67b9 100644
--- a/lib/Path/Dispatcher/Match.pm
+++ b/lib/Path/Dispatcher/Match.pm
@@ -29,7 +29,7 @@ has set_number_vars => (
     is      => 'rw',
     isa     => 'Bool',
     lazy    => 1,
-    default => sub { ref(shift->result) eq 'ARRAY' ? 1 : 0 },
+    default => sub { ref(shift->result) eq 'ARRAY' },
 );
 
 sub run {

commit 358f3fb093222345fbce7c8c6a4e67a06f939315
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:39:59 2009 -0400

    Minimum version 5.8.1

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 94a2ee3..0861083 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -1,5 +1,6 @@
 package Path::Dispatcher;
 use Any::Moose;
+use 5.008001;
 
 our $VERSION = '0.13';
 

commit b9f3559b26eb05279fdb64f1c770b859fe280c29
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:40:50 2009 -0400

    Bump Module::Install

diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index e6758c9..51eda5d 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -17,12 +17,10 @@ package Module::Install;
 #     3. The ./inc/ version of Module::Install loads
 # }
 
-BEGIN {
-	require 5.004;
-}
+use 5.005;
 use strict 'vars';
 
-use vars qw{$VERSION};
+use vars qw{$VERSION $MAIN};
 BEGIN {
 	# All Module::Install core packages now require synchronised versions.
 	# This will be used to ensure we don't accidentally load old or
@@ -30,7 +28,14 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '0.70';
+	$VERSION = '0.91';
+
+	# Storage for the pseudo-singleton
+	$MAIN    = undef;
+
+	*inc::Module::Install::VERSION = *VERSION;
+	@inc::Module::Install::ISA     = __PACKAGE__;
+
 }
 
 
@@ -65,15 +70,26 @@ END_DIE
 # again. This is bad. Rather than taking action to touch it (which
 # is unreliable on some platforms and requires write permissions)
 # for now we should catch this and refuse to run.
-if ( -f $0 and (stat($0))[9] > time ) { die <<"END_DIE" }
+if ( -f $0 ) {
+	my $s = (stat($0))[9];
+
+	# If the modification time is only slightly in the future,
+	# sleep briefly to remove the problem.
+	my $a = $s - time;
+	if ( $a > 0 and $a < 5 ) { sleep 5 }
 
-Your installer $0 has a modification time in the future.
+	# Too far in the future, throw an error.
+	my $t = time;
+	if ( $s > $t ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future ($s > $t).
 
 This is known to create infinite loops in make.
 
 Please correct this, then run $0 again.
 
 END_DIE
+}
 
 
 
@@ -81,7 +97,7 @@ END_DIE
 
 # Build.PL was formerly supported, but no longer is due to excessive
 # difficulty in implementing every single feature twice.
-if ( $0 =~ /Build.PL$/i or -f 'Build.PL' ) { die <<"END_DIE" }
+if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
 
 Module::Install no longer supports Build.PL.
 
@@ -95,14 +111,20 @@ END_DIE
 
 
 
+# To save some more typing in Module::Install installers, every...
+# use inc::Module::Install
+# ...also acts as an implicit use strict.
+$^H |= strict::bits(qw(refs subs vars));
+
+
+
+
+
 use Cwd        ();
 use File::Find ();
 use File::Path ();
 use FindBin;
 
-*inc::Module::Install::VERSION = *VERSION;
- at inc::Module::Install::ISA     = __PACKAGE__;
-
 sub autoload {
 	my $self = shift;
 	my $who  = $self->_caller;
@@ -111,12 +133,22 @@ sub autoload {
 	$sym->{$cwd} = sub {
 		my $pwd = Cwd::cwd();
 		if ( my $code = $sym->{$pwd} ) {
-			# delegate back to parent dirs
+			# Delegate back to parent dirs
 			goto &$code unless $cwd eq $pwd;
 		}
 		$$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+		my $method = $1;
+		if ( uc($method) eq $method ) {
+			# Do nothing
+			return;
+		} elsif ( $method =~ /^_/ and $self->can($method) ) {
+			# Dispatch to the root M:I class
+			return $self->$method(@_);
+		}
+
+		# Dispatch to the appropriate plugin
 		unshift @_, ( $self, $1 );
-		goto &{$self->can('call')} unless uc($1) eq $1;
+		goto &{$self->can('call')};
 	};
 }
 
@@ -141,12 +173,14 @@ sub import {
 	delete $INC{"$self->{file}"};
 	delete $INC{"$self->{path}.pm"};
 
+	# Save to the singleton
+	$MAIN = $self;
+
 	return 1;
 }
 
 sub preload {
-	my ($self) = @_;
-
+	my $self = shift;
 	unless ( $self->{extensions} ) {
 		$self->load_extensions(
 			"$self->{prefix}/$self->{path}", $self
@@ -155,8 +189,7 @@ sub preload {
 
 	my @exts = @{$self->{extensions}};
 	unless ( @exts ) {
-		my $admin = $self->{admin};
-		@exts = $admin->load_all_extensions;
+		@exts = $self->{admin}->load_all_extensions;
 	}
 
 	my %seen;
@@ -202,6 +235,7 @@ sub new {
 		$args{path}  =~ s!::!/!g;
 	}
 	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+	$args{wrote}      = 0;
 
 	bless( \%args, $class );
 }
@@ -238,7 +272,7 @@ END_DIE
 sub load_extensions {
 	my ($self, $path, $top) = @_;
 
-	unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+	unless ( grep { ! ref $_ and lc $_ eq lc $self->{prefix} } @INC ) {
 		unshift @INC, $self->{prefix};
 	}
 
@@ -277,9 +311,9 @@ sub find_extensions {
 		# correctly.  Otherwise, root through the file to locate the case-preserved
 		# version of the package name.
 		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
-			open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
-			my $in_pod = 0;
-			while ( <PKGFILE> ) {
+			my $content = Module::Install::_read($subpath . '.pm');
+			my $in_pod  = 0;
+			foreach ( split //, $content ) {
 				$in_pod = 1 if /^=\w/;
 				$in_pod = 0 if /^=cut/;
 				next if ($in_pod || /^=cut/);  # skip pod text
@@ -289,7 +323,6 @@ sub find_extensions {
 					last;
 				}
 			}
-			close PKGFILE;
 		}
 
 		push @found, [ $file, $pkg ];
@@ -298,6 +331,13 @@ sub find_extensions {
 	@found;
 }
 
+
+
+
+
+#####################################################################
+# Common Utility Functions
+
 sub _caller {
 	my $depth = 0;
 	my $call  = caller($depth);
@@ -308,6 +348,83 @@ sub _caller {
 	return $call;
 }
 
+sub _read {
+	local *FH;
+	if ( $] >= 5.006 ) {
+		open( FH, '<', $_[0] ) or die "open($_[0]): $!";
+	} else {
+		open( FH, "< $_[0]"  ) or die "open($_[0]): $!";
+	}
+	my $string = do { local $/; <FH> };
+	close FH or die "close($_[0]): $!";
+	return $string;
+}
+
+sub _readperl {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	$string =~ s/(\n)\n*__(?:DATA|END)__\b.*\z/$1/s;
+	$string =~ s/\n\n=\w+.+?\n\n=cut\b.+?\n+/\n\n/sg;
+	return $string;
+}
+
+sub _readpod {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	return $string if $_[0] =~ /\.pod\z/;
+	$string =~ s/(^|\n=cut\b.+?\n+)[^=\s].+?\n(\n=\w+|\z)/$1$2/sg;
+	$string =~ s/\n*=pod\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/\n*=cut\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/^\n+//s;
+	return $string;
+}
+
+sub _write {
+	local *FH;
+	if ( $] >= 5.006 ) {
+		open( FH, '>', $_[0] ) or die "open($_[0]): $!";
+	} else {
+		open( FH, "> $_[0]"  ) or die "open($_[0]): $!";
+	}
+	foreach ( 1 .. $#_ ) {
+		print FH $_[$_] or die "print($_[0]): $!";
+	}
+	close FH or die "close($_[0]): $!";
+}
+
+# _version is for processing module versions (eg, 1.03_05) not
+# Perl versions (eg, 5.8.1).
+sub _version ($) {
+	my $s = shift || 0;
+	my $d =()= $s =~ /(\.)/g;
+	if ( $d >= 2 ) {
+		# Normalise multipart versions
+		$s =~ s/(\.)(\d{1,3})/sprintf("$1%03d",$2)/eg;
+	}
+	$s =~ s/^(\d+)\.?//;
+	my $l = $1 || 0;
+	my @v = map {
+		$_ . '0' x (3 - length $_)
+	} $s =~ /(\d{1,3})\D?/g;
+	$l = $l . '.' . join '', @v if @v;
+	return $l + 0;
+}
+
+sub _cmp ($$) {
+	_version($_[0]) <=> _version($_[1]);
+}
+
+# Cloned from Params::Util::_CLASS
+sub _CLASS ($) {
+	(
+		defined $_[0]
+		and
+		! ref $_[0]
+		and
+		$_[0] =~ m/^[^\W\d]\w*(?:::\w+)*\z/s
+	) ? $_[0] : undef;
+}
+
 1;
 
-# Copyright 2008 Adam Kennedy.
+# Copyright 2008 - 2009 Adam Kennedy.
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index 5e24ae1..60a74d2 100644
--- a/inc/Module/Install/Base.pm
+++ b/inc/Module/Install/Base.pm
@@ -1,7 +1,11 @@
 #line 1
 package Module::Install::Base;
 
-$VERSION = '0.70';
+use strict 'vars';
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '0.91';
+}
 
 # Suspend handler for "redefined" warnings
 BEGIN {
@@ -9,52 +13,56 @@ BEGIN {
 	$SIG{__WARN__} = sub { $w };
 }
 
-### This is the ONLY module that shouldn't have strict on
-# use strict;
-
-#line 41
+#line 42
 
 sub new {
-    my ($class, %args) = @_;
-
-    foreach my $method ( qw(call load) ) {
-        *{"$class\::$method"} = sub {
-            shift()->_top->$method(@_);
-        } unless defined &{"$class\::$method"};
-    }
-
-    bless( \%args, $class );
+	my $class = shift;
+	unless ( defined &{"${class}::call"} ) {
+		*{"${class}::call"} = sub { shift->_top->call(@_) };
+	}
+	unless ( defined &{"${class}::load"} ) {
+		*{"${class}::load"} = sub { shift->_top->load(@_) };
+	}
+	bless { @_ }, $class;
 }
 
 #line 61
 
 sub AUTOLOAD {
-    my $self = shift;
-    local $@;
-    my $autoload = eval { $self->_top->autoload } or return;
-    goto &$autoload;
+	local $@;
+	my $func = eval { shift->_top->autoload } or return;
+	goto &$func;
 }
 
-#line 76
+#line 75
 
-sub _top { $_[0]->{_top} }
+sub _top {
+	$_[0]->{_top};
+}
 
-#line 89
+#line 90
 
 sub admin {
-    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+	$_[0]->_top->{admin}
+	or
+	Module::Install::Base::FakeAdmin->new;
 }
 
+#line 106
+
 sub is_admin {
-    $_[0]->admin->VERSION;
+	$_[0]->admin->VERSION;
 }
 
 sub DESTROY {}
 
 package Module::Install::Base::FakeAdmin;
 
-my $Fake;
-sub new { $Fake ||= bless(\@_, $_[0]) }
+my $fake;
+
+sub new {
+	$fake ||= bless(\@_, $_[0]);
+}
 
 sub AUTOLOAD {}
 
@@ -67,4 +75,4 @@ BEGIN {
 
 1;
 
-#line 138
+#line 154
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index 9ce21a4..e65e4f6 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -2,18 +2,16 @@
 package Module::Install::Can;
 
 use strict;
-use Module::Install::Base;
-use Config ();
-### This adds a 5.005 Perl version dependency.
-### This is a bug and will be fixed.
-use File::Spec ();
-use ExtUtils::MakeMaker ();
-
-use vars qw{$VERSION $ISCORE @ISA};
+use Config                ();
+use File::Spec            ();
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
 }
 
 # check if we can load some module
@@ -39,6 +37,7 @@ sub can_run {
 	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
 
 	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		next if $dir eq '';
 		my $abs = File::Spec->catfile($dir, $_[1]);
 		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
 	}
@@ -79,4 +78,4 @@ if ( $^O eq 'cygwin' ) {
 
 __END__
 
-#line 157
+#line 156
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 2b8f6e8..05f2079 100644
--- a/inc/Module/Install/Fetch.pm
+++ b/inc/Module/Install/Fetch.pm
@@ -2,24 +2,24 @@
 package Module::Install::Fetch;
 
 use strict;
-use Module::Install::Base;
+use Module::Install::Base ();
 
-use vars qw{$VERSION $ISCORE @ISA};
+use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
 }
 
 sub get_file {
     my ($self, %args) = @_;
-    my ($scheme, $host, $path, $file) = 
+    my ($scheme, $host, $path, $file) =
         $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
 
     if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
         $args{url} = $args{ftp_url}
             or (warn("LWP support unavailable!\n"), return);
-        ($scheme, $host, $path, $file) = 
+        ($scheme, $host, $path, $file) =
             $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
     }
 
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index 27bbace..98779db 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -2,14 +2,14 @@
 package Module::Install::Makefile;
 
 use strict 'vars';
-use Module::Install::Base;
-use ExtUtils::MakeMaker ();
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
 
-use vars qw{$VERSION $ISCORE @ISA};
+use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
 }
 
 sub Makefile { $_[0] }
@@ -36,9 +36,9 @@ sub prompt {
 
 sub makemaker_args {
 	my $self = shift;
-	my $args = ($self->{makemaker_args} ||= {});
-	  %$args = ( %$args, @_ ) if @_;
-	$args;
+	my $args = ( $self->{makemaker_args} ||= {} );
+	%$args = ( %$args, @_ );
+	return $args;
 }
 
 # For mm args that take multiple space-seperated args,
@@ -63,18 +63,18 @@ sub build_subdirs {
 sub clean_files {
 	my $self  = shift;
 	my $clean = $self->makemaker_args->{clean} ||= {};
-	%$clean = (
-		%$clean, 
-		FILES => join(' ', grep length, $clean->{FILES}, @_),
+	  %$clean = (
+		%$clean,
+		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
 	);
 }
 
 sub realclean_files {
-	my $self  = shift;
+	my $self      = shift;
 	my $realclean = $self->makemaker_args->{realclean} ||= {};
-	%$realclean = (
-		%$realclean, 
-		FILES => join(' ', grep length, $realclean->{FILES}, @_),
+	  %$realclean = (
+		%$realclean,
+		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
 	);
 }
 
@@ -114,20 +114,41 @@ sub write {
 	my $self = shift;
 	die "&Makefile->write() takes no arguments\n" if @_;
 
-	# Make sure we have a new enough
+	# Check the current Perl version
+	my $perl_version = $self->perl_version;
+	if ( $perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	# Make sure we have a new enough MakeMaker
 	require ExtUtils::MakeMaker;
-	$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION );
 
-	# Generate the 
+	if ( $perl_version and $self->_cmp($perl_version, '5.006') >= 0 ) {
+		# MakeMaker can complain about module versions that include
+		# an underscore, even though its own version may contain one!
+		# Hence the funny regexp to get rid of it.  See RT #35800
+		# for details.
+		$self->build_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+	} else {
+		# Allow legacy-compatibility with 5.005 by depending on the
+		# most recent EU:MM that supported 5.005.
+		$self->build_requires( 'ExtUtils::MakeMaker' => 6.42 );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => 6.42 );
+	}
+
+	# Generate the MakeMaker params
 	my $args = $self->makemaker_args;
 	$args->{DISTNAME} = $self->name;
-	$args->{NAME}     = $self->module_name || $self->name || $self->determine_NAME($args);
-	$args->{VERSION}  = $self->version || $self->determine_VERSION($args);
+	$args->{NAME}     = $self->module_name || $self->name;
+	$args->{VERSION}  = $self->version;
 	$args->{NAME}     =~ s/-/::/g;
 	if ( $self->tests ) {
 		$args->{test} = { TESTS => $self->tests };
 	}
-	if ($] >= 5.005) {
+	if ( $] >= 5.005 ) {
 		$args->{ABSTRACT} = $self->abstract;
 		$args->{AUTHOR}   = $self->author;
 	}
@@ -141,7 +162,7 @@ sub write {
 		delete $args->{SIGN};
 	}
 
-	# merge both kinds of requires into prereq_pm
+	# Merge both kinds of requires into prereq_pm
 	my $prereq = ($args->{PREREQ_PM} ||= {});
 	%$prereq = ( %$prereq,
 		map { @$_ }
@@ -175,7 +196,9 @@ sub write {
 
 	my $user_preop = delete $args{dist}->{PREOP};
 	if (my $preop = $self->admin->preop($user_preop)) {
-		$args{dist} = $preop;
+		foreach my $key ( keys %$preop ) {
+			$args{dist}->{$key} = $preop->{$key};
+		}
 	}
 
 	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
@@ -188,7 +211,7 @@ sub fix_up_makefile {
 	my $top_class     = ref($self->_top) || '';
 	my $top_version   = $self->_top->VERSION || '';
 
-	my $preamble = $self->preamble 
+	my $preamble = $self->preamble
 		? "# Preamble by $top_class $top_version\n"
 			. $self->preamble
 		: '';
@@ -242,4 +265,4 @@ sub postamble {
 
 __END__
 
-#line 371
+#line 394
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index a39ffde..653193d 100644
--- a/inc/Module/Install/Metadata.pm
+++ b/inc/Module/Install/Metadata.pm
@@ -2,117 +2,254 @@
 package Module::Install::Metadata;
 
 use strict 'vars';
-use Module::Install::Base;
+use Module::Install::Base ();
 
-use vars qw{$VERSION $ISCORE @ISA};
+use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
 }
 
+my @boolean_keys = qw{
+	sign
+};
+
 my @scalar_keys = qw{
-	name module_name abstract author version license
-	distribution_type perl_version tests installdirs
+	name
+	module_name
+	abstract
+	author
+	version
+	distribution_type
+	tests
+	installdirs
 };
 
 my @tuple_keys = qw{
-	configure_requires build_requires requires recommends bundles
+	configure_requires
+	build_requires
+	requires
+	recommends
+	bundles
+	resources
 };
 
-sub Meta            { shift        }
-sub Meta_ScalarKeys { @scalar_keys }
-sub Meta_TupleKeys  { @tuple_keys  }
+my @resource_keys = qw{
+	homepage
+	bugtracker
+	repository
+};
+
+my @array_keys = qw{
+	keywords
+};
 
-foreach my $key (@scalar_keys) {
+sub Meta              { shift          }
+sub Meta_BooleanKeys  { @boolean_keys  }
+sub Meta_ScalarKeys   { @scalar_keys   }
+sub Meta_TupleKeys    { @tuple_keys    }
+sub Meta_ResourceKeys { @resource_keys }
+sub Meta_ArrayKeys    { @array_keys    }
+
+foreach my $key ( @boolean_keys ) {
 	*$key = sub {
 		my $self = shift;
-		return $self->{values}{$key} if defined wantarray and !@_;
-		$self->{values}{$key} = shift;
+		if ( defined wantarray and not @_ ) {
+			return $self->{values}->{$key};
+		}
+		$self->{values}->{$key} = ( @_ ? $_[0] : 1 );
 		return $self;
 	};
 }
 
-foreach my $key (@tuple_keys) {
+foreach my $key ( @scalar_keys ) {
 	*$key = sub {
 		my $self = shift;
-		return $self->{values}{$key} unless @_;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} = shift;
+		return $self;
+	};
+}
+
+foreach my $key ( @array_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} ||= [];
+		push @{$self->{values}->{$key}}, @_;
+		return $self;
+	};
+}
 
-		my @rv;
-		while (@_) {
-			my $module = shift or last;
+foreach my $key ( @resource_keys ) {
+	*$key = sub {
+		my $self = shift;
+		unless ( @_ ) {
+			return () unless $self->{values}->{resources};
+			return map  { $_->[1] }
+			       grep { $_->[0] eq $key }
+			       @{ $self->{values}->{resources} };
+		}
+		return $self->{values}->{resources}->{$key} unless @_;
+		my $uri = shift or die(
+			"Did not provide a value to $key()"
+		);
+		$self->resources( $key => $uri );
+		return 1;
+	};
+}
+
+foreach my $key ( grep { $_ ne "resources" } @tuple_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} unless @_;
+		my @added;
+		while ( @_ ) {
+			my $module  = shift or last;
 			my $version = shift || 0;
-			if ( $module eq 'perl' ) {
-				$version =~ s{^(\d+)\.(\d+)\.(\d+)}
-				             {$1 + $2/1_000 + $3/1_000_000}e;
-				$self->perl_version($version);
-				next;
-			}
-			my $rv = [ $module, $version ];
-			push @rv, $rv;
+			push @added, [ $module, $version ];
 		}
-		push @{ $self->{values}{$key} }, @rv;
-		@rv;
+		push @{ $self->{values}->{$key} }, @added;
+		return map {@$_} @added;
 	};
 }
 
+# Resource handling
+my %lc_resource = map { $_ => 1 } qw{
+	homepage
+	license
+	bugtracker
+	repository
+};
+
+sub resources {
+	my $self = shift;
+	while ( @_ ) {
+		my $name  = shift or last;
+		my $value = shift or next;
+		if ( $name eq lc $name and ! $lc_resource{$name} ) {
+			die("Unsupported reserved lowercase resource '$name'");
+		}
+		$self->{values}->{resources} ||= [];
+		push @{ $self->{values}->{resources} }, [ $name, $value ];
+	}
+	$self->{values}->{resources};
+}
+
 # Aliases for build_requires that will have alternative
 # meanings in some future version of META.yml.
-sub test_requires      { shift->build_requires(@_)  }
-sub install_requires   { shift->build_requires(@_)  }
+sub test_requires     { shift->build_requires(@_) }
+sub install_requires  { shift->build_requires(@_) }
 
 # Aliases for installdirs options
-sub install_as_core    { $_[0]->installdirs('perl')   }
-sub install_as_cpan    { $_[0]->installdirs('site')   }
-sub install_as_site    { $_[0]->installdirs('site')   }
-sub install_as_vendor  { $_[0]->installdirs('vendor') }
-
-sub sign {
-	my $self = shift;
-	return $self->{'values'}{'sign'} if defined wantarray and ! @_;
-	$self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
-	return $self;
-}
+sub install_as_core   { $_[0]->installdirs('perl')   }
+sub install_as_cpan   { $_[0]->installdirs('site')   }
+sub install_as_site   { $_[0]->installdirs('site')   }
+sub install_as_vendor { $_[0]->installdirs('vendor') }
 
 sub dynamic_config {
 	my $self = shift;
 	unless ( @_ ) {
-		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+		warn "You MUST provide an explicit true/false value to dynamic_config\n";
 		return $self;
 	}
-	$self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
-	return $self;
+	$self->{values}->{dynamic_config} = $_[0] ? 1 : 0;
+	return 1;
+}
+
+sub perl_version {
+	my $self = shift;
+	return $self->{values}->{perl_version} unless @_;
+	my $version = shift or die(
+		"Did not provide a value to perl_version()"
+	);
+
+	# Normalize the version
+	$version = $self->_perl_version($version);
+
+	# We don't support the reall old versions
+	unless ( $version >= 5.005 ) {
+		die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
+	}
+
+	$self->{values}->{perl_version} = $version;
+}
+
+#Stolen from M::B
+my %license_urls = (
+    perl         => 'http://dev.perl.org/licenses/',
+    apache       => 'http://apache.org/licenses/LICENSE-2.0',
+    artistic     => 'http://opensource.org/licenses/artistic-license.php',
+    artistic_2   => 'http://opensource.org/licenses/artistic-license-2.0.php',
+    lgpl         => 'http://opensource.org/licenses/lgpl-license.php',
+    lgpl2        => 'http://opensource.org/licenses/lgpl-2.1.php',
+    lgpl3        => 'http://opensource.org/licenses/lgpl-3.0.html',
+    bsd          => 'http://opensource.org/licenses/bsd-license.php',
+    gpl          => 'http://opensource.org/licenses/gpl-license.php',
+    gpl2         => 'http://opensource.org/licenses/gpl-2.0.php',
+    gpl3         => 'http://opensource.org/licenses/gpl-3.0.html',
+    mit          => 'http://opensource.org/licenses/mit-license.php',
+    mozilla      => 'http://opensource.org/licenses/mozilla1.1.php',
+    open_source  => undef,
+    unrestricted => undef,
+    restrictive  => undef,
+    unknown      => undef,
+);
+
+sub license {
+	my $self = shift;
+	return $self->{values}->{license} unless @_;
+	my $license = shift or die(
+		'Did not provide a value to license()'
+	);
+	$self->{values}->{license} = $license;
+
+	# Automatically fill in license URLs
+	if ( $license_urls{$license} ) {
+		$self->resources( license => $license_urls{$license} );
+	}
+
+	return 1;
 }
 
 sub all_from {
 	my ( $self, $file ) = @_;
 
 	unless ( defined($file) ) {
-		my $name = $self->name
-			or die "all_from called with no args without setting name() first";
+		my $name = $self->name or die(
+			"all_from called with no args without setting name() first"
+		);
 		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
 		$file =~ s{.*/}{} unless -e $file;
-		die "all_from: cannot find $file from $name" unless -e $file;
+		unless ( -e $file ) {
+			die("all_from cannot find $file from $name");
+		}
 	}
+	unless ( -f $file ) {
+		die("The path '$file' does not exist, or is not a file");
+	}
+
+	# Some methods pull from POD instead of code.
+	# If there is a matching .pod, use that instead
+	my $pod = $file;
+	$pod =~ s/\.pm$/.pod/i;
+	$pod = $file unless -e $pod;
 
+	# Pull the different values
+	$self->name_from($file)         unless $self->name;
 	$self->version_from($file)      unless $self->version;
 	$self->perl_version_from($file) unless $self->perl_version;
+	$self->author_from($pod)        unless $self->author;
+	$self->license_from($pod)       unless $self->license;
+	$self->abstract_from($pod)      unless $self->abstract;
 
-	# The remaining probes read from POD sections; if the file
-	# has an accompanying .pod, use that instead
-	my $pod = $file;
-	if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
-		$file = $pod;
-	}
-
-	$self->author_from($file)   unless $self->author;
-	$self->license_from($file)  unless $self->license;
-	$self->abstract_from($file) unless $self->abstract;
+	return 1;
 }
 
 sub provides {
 	my $self     = shift;
-	my $provides = ( $self->{values}{provides} ||= {} );
+	my $provides = ( $self->{values}->{provides} ||= {} );
 	%$provides = (%$provides, @_) if @_;
 	return $provides;
 }
@@ -141,7 +278,7 @@ sub auto_provides {
 sub feature {
 	my $self     = shift;
 	my $name     = shift;
-	my $features = ( $self->{values}{features} ||= [] );
+	my $features = ( $self->{values}->{features} ||= [] );
 	my $mods;
 
 	if ( @_ == 1 and ref( $_[0] ) ) {
@@ -177,16 +314,16 @@ sub features {
 sub no_index {
 	my $self = shift;
 	my $type = shift;
-	push @{ $self->{values}{no_index}{$type} }, @_ if $type;
-	return $self->{values}{no_index};
+	push @{ $self->{values}->{no_index}->{$type} }, @_ if $type;
+	return $self->{values}->{no_index};
 }
 
 sub read {
 	my $self = shift;
-	$self->include_deps( 'YAML', 0 );
+	$self->include_deps( 'YAML::Tiny', 0 );
 
-	require YAML;
-	my $data = YAML::LoadFile('META.yml');
+	require YAML::Tiny;
+	my $data = YAML::Tiny::LoadFile('META.yml');
 
 	# Call methods explicitly in case user has already set some values.
 	while ( my ( $key, $value ) = each %$data ) {
@@ -226,35 +363,51 @@ sub abstract_from {
 	 );
 }
 
-sub _slurp {
-	local *FH;
-	open FH, "< $_[1]" or die "Cannot open $_[1].pod: $!";
-	do { local $/; <FH> };
+# Add both distribution and module name
+sub name_from {
+	my ($self, $file) = @_;
+	if (
+		Module::Install::_read($file) =~ m/
+		^ \s*
+		package \s*
+		([\w:]+)
+		\s* ;
+		/ixms
+	) {
+		my ($name, $module_name) = ($1, $1);
+		$name =~ s{::}{-}g;
+		$self->name($name);
+		unless ( $self->module_name ) {
+			$self->module_name($module_name);
+		}
+	} else {
+		die("Cannot determine name from $file\n");
+	}
 }
 
 sub perl_version_from {
-	my ( $self, $file ) = @_;
+	my $self = shift;
 	if (
-		$self->_slurp($file) =~ m/
+		Module::Install::_read($_[0]) =~ m/
 		^
-		use \s*
+		(?:use|require) \s*
 		v?
 		([\d_\.]+)
 		\s* ;
 		/ixms
 	) {
-		my $v = $1;
-		$v =~ s{_}{}g;
-		$self->perl_version($1);
+		my $perl_version = $1;
+		$perl_version =~ s{_}{}g;
+		$self->perl_version($perl_version);
 	} else {
-		warn "Cannot determine perl version info from $file\n";
+		warn "Cannot determine perl version info from $_[0]\n";
 		return;
 	}
 }
 
 sub author_from {
-	my ( $self, $file ) = @_;
-	my $content = $self->_slurp($file);
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
 	if ($content =~ m/
 		=head \d \s+ (?:authors?)\b \s*
 		([^\n]*)
@@ -268,15 +421,14 @@ sub author_from {
 		$author =~ s{E<gt>}{>}g;
 		$self->author($author);
 	} else {
-		warn "Cannot determine author info from $file\n";
+		warn "Cannot determine author info from $_[0]\n";
 	}
 }
 
 sub license_from {
-	my ( $self, $file ) = @_;
-
+	my $self = shift;
 	if (
-		$self->_slurp($file) =~ m/
+		Module::Install::_read($_[0]) =~ m/
 		(
 			=head \d \s+
 			(?:licen[cs]e|licensing|copyright|legal)\b
@@ -287,32 +439,186 @@ sub license_from {
 	/ixms ) {
 		my $license_text = $1;
 		my @phrases      = (
-			'under the same (?:terms|license) as perl itself' => 'perl',        1,
-			'GNU public license'                              => 'gpl',         1,
-			'GNU lesser public license'                       => 'lgpl',        1,
-			'BSD license'                                     => 'bsd',         1,
-			'Artistic license'                                => 'artistic',    1,
-			'GPL'                                             => 'gpl',         1,
-			'LGPL'                                            => 'lgpl',        1,
-			'BSD'                                             => 'bsd',         1,
-			'Artistic'                                        => 'artistic',    1,
-			'MIT'                                             => 'mit',         1,
-			'proprietary'                                     => 'proprietary', 0,
+			'under the same (?:terms|license) as (?:perl|the perl programming language) itself' => 'perl', 1,
+			'GNU general public license'         => 'gpl',         1,
+			'GNU public license'                 => 'gpl',         1,
+			'GNU lesser general public license'  => 'lgpl',        1,
+			'GNU lesser public license'          => 'lgpl',        1,
+			'GNU library general public license' => 'lgpl',        1,
+			'GNU library public license'         => 'lgpl',        1,
+			'BSD license'                        => 'bsd',         1,
+			'Artistic license'                   => 'artistic',    1,
+			'GPL'                                => 'gpl',         1,
+			'LGPL'                               => 'lgpl',        1,
+			'BSD'                                => 'bsd',         1,
+			'Artistic'                           => 'artistic',    1,
+			'MIT'                                => 'mit',         1,
+			'proprietary'                        => 'proprietary', 0,
 		);
 		while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
 			$pattern =~ s{\s+}{\\s+}g;
 			if ( $license_text =~ /\b$pattern\b/i ) {
-				if ( $osi and $license_text =~ /All rights reserved/i ) {
-					warn "LEGAL WARNING: 'All rights reserved' may invalidate Open Source licenses. Consider removing it.";
-				}
 				$self->license($license);
 				return 1;
 			}
 		}
 	}
 
-	warn "Cannot determine license info from $file\n";
+	warn "Cannot determine license info from $_[0]\n";
 	return 'unknown';
 }
 
+sub _extract_bugtracker {
+	my @links   = $_[0] =~ m#L<(\Qhttp://rt.cpan.org/\E[^>]+)>#g;
+	my %links;
+	@links{@links}=();
+	@links=keys %links;
+	return @links;
+}
+
+sub bugtracker_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	my @links   = _extract_bugtracker($content);
+	unless ( @links ) {
+		warn "Cannot determine bugtracker info from $_[0]\n";
+		return 0;
+	}
+	if ( @links > 1 ) {
+		warn "Found more than on rt.cpan.org link in $_[0]\n";
+		return 0;
+	}
+
+	# Set the bugtracker
+	bugtracker( $links[0] );
+	return 1;
+}
+
+sub requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->requires( $module => $version );
+	}
+}
+
+sub test_requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->test_requires( $module => $version );
+	}
+}
+
+# Convert triple-part versions (eg, 5.6.1 or 5.8.9) to
+# numbers (eg, 5.006001 or 5.008009).
+# Also, convert double-part versions (eg, 5.8)
+sub _perl_version {
+	my $v = $_[-1];
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
+	$v =~ s/(\.\d\d\d)000$/$1/;
+	$v =~ s/_.+$//;
+	if ( ref($v) ) {
+		# Numify
+		$v = $v + 0;
+	}
+	return $v;
+}
+
+
+
+
+
+######################################################################
+# MYMETA Support
+
+sub WriteMyMeta {
+	die "WriteMyMeta has been deprecated";
+}
+
+sub write_mymeta_yaml {
+	my $self = shift;
+
+	# We need YAML::Tiny to write the MYMETA.yml file
+	unless ( eval { require YAML::Tiny; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.yml\n";
+	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
+}
+
+sub write_mymeta_json {
+	my $self = shift;
+
+	# We need JSON to write the MYMETA.json file
+	unless ( eval { require JSON; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.json\n";
+	Module::Install::_write(
+		'MYMETA.json',
+		JSON->new->pretty(1)->canonical->encode($meta),
+	);
+}
+
+sub _write_mymeta_data {
+	my $self = shift;
+
+	# If there's no existing META.yml there is nothing we can do
+	return undef unless -f 'META.yml';
+
+	# We need Parse::CPAN::Meta to load the file
+	unless ( eval { require Parse::CPAN::Meta; 1; } ) {
+		return undef;
+	}
+
+	# Merge the perl version into the dependencies
+	my $val  = $self->Meta->{values};
+	my $perl = delete $val->{perl_version};
+	if ( $perl ) {
+		$val->{requires} ||= [];
+		my $requires = $val->{requires};
+
+		# Canonize to three-dot version after Perl 5.6
+		if ( $perl >= 5.006 ) {
+			$perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e
+		}
+		unshift @$requires, [ perl => $perl ];
+	}
+
+	# Load the advisory META.yml file
+	my @yaml = Parse::CPAN::Meta::LoadFile('META.yml');
+	my $meta = $yaml[0];
+
+	# Overwrite the non-configure dependency hashs
+	delete $meta->{requires};
+	delete $meta->{build_requires};
+	delete $meta->{recommends};
+	if ( exists $val->{requires} ) {
+		$meta->{requires} = { map { @$_ } @{ $val->{requires} } };
+	}
+	if ( exists $val->{build_requires} ) {
+		$meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } };
+	}
+
+	return $meta;
+}
+
 1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index 21a81ab..f2f99df 100644
--- a/inc/Module/Install/Win32.pm
+++ b/inc/Module/Install/Win32.pm
@@ -2,12 +2,12 @@
 package Module::Install::Win32;
 
 use strict;
-use Module::Install::Base;
+use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
-	@ISA     = qw{Module::Install::Base};
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
 
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index a05592d..12471e5 100644
--- a/inc/Module/Install/WriteAll.pm
+++ b/inc/Module/Install/WriteAll.pm
@@ -2,11 +2,11 @@
 package Module::Install::WriteAll;
 
 use strict;
-use Module::Install::Base;
+use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '0.70';
+	$VERSION = '0.91';;
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
@@ -22,7 +22,6 @@ sub WriteAll {
 	);
 
 	$self->sign(1)                if $args{sign};
-	$self->Meta->write            if $args{meta};
 	$self->admin->WriteAll(%args) if $self->is_admin;
 
 	$self->check_nmake if $args{check_nmake};
@@ -30,11 +29,32 @@ sub WriteAll {
 		$self->makemaker_args( PL_FILES => {} );
 	}
 
+	# Until ExtUtils::MakeMaker support MYMETA.yml, make sure
+	# we clean it up properly ourself.
+	$self->realclean_files('MYMETA.yml');
+
 	if ( $args{inline} ) {
 		$self->Inline->write;
 	} else {
 		$self->Makefile->write;
 	}
+
+	# The Makefile write process adds a couple of dependencies,
+	# so write the META.yml files after the Makefile.
+	if ( $args{meta} ) {
+		$self->Meta->write;
+	}
+
+	# Experimental support for MYMETA
+	if ( $ENV{X_MYMETA} ) {
+		if ( $ENV{X_MYMETA} eq 'JSON' ) {
+			$self->Meta->write_mymeta_json;
+		} else {
+			$self->Meta->write_mymeta_yaml;
+		}
+	}
+
+	return 1;
 }
 
 1;

commit 1c2b29c2f11b076f9e7656d8f5a3e025dfbfc692
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:42:55 2009 -0400

    Makefile.PL tweaks

diff --git a/Makefile.PL b/Makefile.PL
index b526bfd..6f8707b 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,12 +1,11 @@
 use inc::Module::Install;
 
-name           'Path-Dispatcher';
-all_from       'lib/Path/Dispatcher.pm';
+name     'Path-Dispatcher';
+all_from 'lib/Path/Dispatcher.pm';
 
 requires 'Any::Moose';
 requires 'Sub::Exporter';
 
-build_requires 'Test::More';
 build_requires 'Test::Exception';
 
 WriteAll;

commit 9f608969935818946938a62d05af7e21aedd09f4
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:45:42 2009 -0400

    Add repository information

diff --git a/Makefile.PL b/Makefile.PL
index 6f8707b..8657a8d 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,7 +1,8 @@
 use inc::Module::Install;
 
-name     'Path-Dispatcher';
-all_from 'lib/Path/Dispatcher.pm';
+name       'Path-Dispatcher';
+all_from   'lib/Path/Dispatcher.pm';
+repository 'http://github.com/bestpractical/path-dispatcher';
 
 requires 'Any::Moose';
 requires 'Sub::Exporter';

commit d45c2c81617cbab9c13bbc545650e10b28f32635
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:46:24 2009 -0400

    Add dist crap to Changes

diff --git a/Changes b/Changes
index d09b268..d3227ce 100644
--- a/Changes
+++ b/Changes
@@ -3,6 +3,8 @@ Revision history for Path-Dispatcher
 0.13 Sun Aug 9 13:38:19 2009
         Add unshift_rule to classes that do Role::Rules
 
+        Several distribution improvements
+
 0.12 Fri Apr 17 03:21:05 2009
         Fix a bug with undefined capture variables being converted to the
             empty string and throwing warnings (reported by obra)

commit 15aaf42a5872be7bb4fcc7de294099393c539b5d
Author: Shawn M Moore <sartak at gmail.com>
Date:   Sun Aug 9 13:47:30 2009 -0400

    Bump to 0.14

diff --git a/Changes b/Changes
index d3227ce..ae9186b 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Path-Dispatcher
 
+0.14
+
 0.13 Sun Aug 9 13:38:19 2009
         Add unshift_rule to classes that do Role::Rules
 
diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 0861083..97175eb 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -2,7 +2,7 @@ package Path::Dispatcher;
 use Any::Moose;
 use 5.008001;
 
-our $VERSION = '0.13';
+our $VERSION = '0.14';
 
 use Path::Dispatcher::Rule;
 use Path::Dispatcher::Dispatch;

commit bebbcd409d413eee785df4b901c85db614b60750
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Oct 16 16:25:40 2009 -0400

    Add failing test for lazy dispatch

diff --git a/t/023-lazy-dispatch.t b/t/023-lazy-dispatch.t
new file mode 100644
index 0000000..588fe72
--- /dev/null
+++ b/t/023-lazy-dispatch.t
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More tests => 1;
+use Path::Dispatcher;
+
+my @calls;
+
+my $dispatcher = Path::Dispatcher->new;
+$dispatcher->add_rule(
+    Path::Dispatcher::Rule::Tokens->new(
+        tokens => ['hello'],
+        block  => sub { push @calls, 'hello' },
+    ),
+    Path::Dispatcher::Rule::CodeRef->new(
+        matcher => sub { fail("should never run") },
+        block  => sub { push @calls, 'fail' },
+    ),
+);
+
+$dispatcher->run('foo bar');
+is_deeply([splice @calls], ['hello']);
+

commit 3f44b9aeb07119f8232180ac2cf696eb520cf145
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Oct 16 18:56:09 2009 -0400

    Remove commented out code

diff --git a/lib/Path/Dispatcher.pm b/lib/Path/Dispatcher.pm
index 97175eb..7de099b 100644
--- a/lib/Path/Dispatcher.pm
+++ b/lib/Path/Dispatcher.pm
@@ -55,9 +55,6 @@ sub dispatch_rule {
 
     my @matches = $args{rule}->match($args{path});
 
-    # Support ::Chain here? Probably not. As ::Chain doesn't make sense unless it is within an ::Under
-#    return if $matches[-1]->rule->isa('Path::Dispatcher::Rule::Chain'); 
-    
     $args{dispatch}->add_matches(@matches);
 
     return @matches;

commit eb5928a6bbc288143532fadba53249fa8c7a62e6
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 20:51:22 2009 -0500

    Revert "Add failing test for lazy dispatch"
    
    This reverts commit bebbcd409d413eee785df4b901c85db614b60750.
    
    Going to work on a branch instead

diff --git a/t/023-lazy-dispatch.t b/t/023-lazy-dispatch.t
deleted file mode 100644
index 588fe72..0000000
--- a/t/023-lazy-dispatch.t
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use Test::More tests => 1;
-use Path::Dispatcher;
-
-my @calls;
-
-my $dispatcher = Path::Dispatcher->new;
-$dispatcher->add_rule(
-    Path::Dispatcher::Rule::Tokens->new(
-        tokens => ['hello'],
-        block  => sub { push @calls, 'hello' },
-    ),
-    Path::Dispatcher::Rule::CodeRef->new(
-        matcher => sub { fail("should never run") },
-        block  => sub { push @calls, 'fail' },
-    ),
-);
-
-$dispatcher->run('foo bar');
-is_deeply([splice @calls], ['hello']);
-

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list