--- /dev/null
+package CatalystX::Routes;
+
+use strict;
+use warnings;
+
+use CatalystX::Routes::Role::Class;
+use CatalystX::Routes::Role::Controller;
+use Moose::Util qw( apply_all_roles );
+use Params::Util qw( _STRING _CODELIKE );
+use Scalar::Util qw( blessed );
+
+use Moose::Exporter;
+
+Moose::Exporter->setup_import_methods(
+ with_meta => [qw( get get_html post put del )],
+ as_is => [qw( chained args capture_args path_part action )],
+ class_metaroles => {
+ class => ['CatalystX::Routes::Role::Class'],
+ },
+);
+
+sub get {
+ _add_route( 'GET', @_ );
+}
+
+sub get_html {
+ _add_route( 'GET_html', @_ );
+}
+
+sub post {
+ _add_route( 'POST', @_ );
+}
+
+sub put {
+ _add_route( 'PUT', @_ );
+}
+
+sub del {
+ _add_route( 'DELETE', @_ );
+}
+
+sub _add_route {
+ my $rest = shift;
+ my $meta = shift;
+ my ( $name, $attrs, $sub ) = _process_args(@_);
+
+ my $meth_base = '__route__' . $name;
+
+ _maybe_add_rest_route( $meta, $meth_base, $attrs );
+
+ my $meth_name = $meth_base . q{_} . $rest;
+
+ $meta->add_method( $meth_name => sub { goto &$sub } );
+
+ return;
+}
+
+sub path_part ($) {
+ return ( PathPart => [ $_[0] ] );
+}
+
+sub chained ($) {
+ return ( Chained => [ $_[0] ] );
+}
+
+sub args ($) {
+ return ( Args => [ $_[0] ] );
+}
+
+sub capture_args ($) {
+ return ( CaptureArgs => [ $_[0] ] );
+}
+
+sub action ($) {
+ return ( ActionClass => [ $_[0] ] );
+}
+
+sub _process_args {
+ my $path = shift;
+ my $sub = pop;
+
+ my $caller = ( caller(2) )[3];
+
+ die
+ "The $caller keyword expects a path string or regex as its first argument"
+ unless _STRINGLIKE0($path) || _REGEX($path);
+
+ die "The $caller keyword expects a sub ref as its final argument"
+ unless _CODELIKE($sub);
+
+ my %p = @_;
+
+ $p{ActionClass} ||= 'REST';
+
+ ( my $name = $path ) =~ s/(\W)/'X' . sprintf( '%x', ord($1) )/eg;
+
+ return $name, \%p, $sub;
+}
+
+sub _maybe_add_rest_route {
+ my $meta = shift;
+ my $name = shift;
+ my $attrs = shift;
+
+ return if $meta->has_method($name);
+
+ # This could be done by Moose::Exporter, but that would require that the
+ # module has already inherited from Cat::Controller when it calls "use
+ # CatalystX::Routes".
+ unless ( $meta->does_role('CatalystX::Routes::Role::Controller') ) {
+ apply_all_roles(
+ $meta->name(),
+ 'CatalystX::Routes::Role::Controller'
+ );
+ }
+
+ $meta->add_method( $name => sub { } );
+
+ $meta->add_route( $name => [ $attrs, $meta->get_method($name) ] );
+
+ return;
+}
+
+# XXX - this should be added to Params::Util
+sub _STRINGLIKE0 ($) {
+ return _STRING( $_[0] )
+ || ( defined $_[0]
+ && $_[0] eq q{} )
+ || ( blessed $_[0]
+ && overload::Method( $_[0], q{""} )
+ && length "$_[0]" );
+}
+
+1;
--- /dev/null
+package CatalystX::Routes::Role::Controller;
+
+use Moose::Role;
+use namespace::clean -except => 'meta';
+
+requires 'register_actions';
+
+after register_actions => sub {
+ my $self = shift;
+ my $c = shift;
+
+ my $class = $self->catalyst_component_name;
+ my $namespace = $self->action_namespace($c);
+
+ for my $route ( $self->meta()->route_names() ) {
+ my ( $attrs, $method ) = @{ $self->meta()->get_route($route) };
+
+ for my $key ( keys %{$attrs} ) {
+ my $parse_meth = "_parse_${key}_attr";
+
+ next unless $self->can($parse_meth);
+
+ ( undef, my $value )
+ = $self->$parse_meth( $c, $route, $attrs->{$key} );
+
+ $attrs->{$key} = [$value];
+ }
+
+ my $reverse = $namespace ? "${namespace}/$route" : $route;
+
+ my $action = $self->create_action(
+ name => $route,
+ code => $method->body(),
+ reverse => $reverse,
+ namespace => $namespace,
+ class => $class,
+ attributes => $attrs,
+ );
+
+ $c->dispatcher->register( $c, $action );
+ }
+};
+
+1;