package Tatsumaki::Application; use AnyEvent; use Any::Moose; use Tatsumaki::Handler; use Tatsumaki::Request; use Try::Tiny; use Plack::Middleware::Static; use overload q(&{}) => sub { shift->psgi_app }, fallback => 1; has _rules => (is => 'rw', isa => 'ArrayRef'); has template => (is => 'rw', isa => 'Tatsumaki::Template', lazy_build => 1, handles => [ 'render_file' ]); has static_path => (is => 'rw', isa => 'Str', default => 'static'); has _services => (is => 'rw', isa => 'HashRef', default => sub { +{} }); around BUILDARGS => sub { my $orig = shift; my $class = shift; if (ref $_[0] eq 'ARRAY') { my $handlers = shift @_; my @rules; while (my($path, $handler) = splice @$handlers, 0, 2) { $path = qr@^/$@ if $path eq '/'; $path = qr/^$path/ unless ref $path eq 'RegExp'; push @rules, { path => $path, handler => $handler }; } $class->$orig(_rules => \@rules, @_); } else { $class->$orig(@_); } }; sub add_handlers { my $self = shift; while (my($path, $handler) = splice @_, 0, 2) { $self->route($path, $handler); } } sub route { my($self, $path, $handler) = @_; $path = qr/^$path/ unless ref $path eq 'RegExp'; push @{$self->_rules}, { path => $path, handler => $handler }; } sub dispatch { my($self, $req) = @_; my $path = $req->path_info; for my $rule (@{$self->_rules}) { if ($path =~ $rule->{path}) { my $args = [ $1, $2, $3, $4, $5, $6, $7, $8, $9 ]; return $rule->{handler}->new(application => $self, request => $req, args => $args); } } return; } sub psgi_app { my $self = shift; return $self->{psgi_app} ||= $self->compile_psgi_app; } sub compile_psgi_app { my $self = shift; my $app = sub { my $env = shift; my $req = Tatsumaki::Request->new($env); my $handler = $self->dispatch($req) or return [ 404, [ 'Content-Type' => 'text/html' ], [ "404 Not Found" ] ]; my $res = $handler->run; }; if ($self->static_path) { $app = Plack::Middleware::Static->wrap($app, path => sub { s/^\/(?:(favicon\.ico)|static\/)/$1||''/e }, root => $self->static_path); } $app; } sub _build_template { my $self = shift; require Tatsumaki::Template::Micro; Tatsumaki::Template::Micro->new; } sub template_path { my $self = shift; if (@_) { my $path = ref $_[0] eq 'ARRAY' ? $_[0] : [ $_[0] ]; $self->template->include_path($path); } $self->template->include_path; } sub add_service { my $self = shift; my($name, $service); if (@_ == 2) { ($name, $service) = @_; } else { $service = shift; $name = $self->_service_name_for($service); } $service->application($self); $service->start; $self->_services->{$name} = $service; } sub service { my($self, $name) = @_; $self->_services->{$name}; } sub services { my $self = shift; values %{$self->_services}; } sub _service_name_for { my($self, $service) = @_; my $ref = ref $service; $ref =~ s/^Tatsumaki::Service:://; my $name = $ref; my $i = 0; while (exists $self->_services->{$name}) { $name = $ref . $i++; } return $name; } no Any::Moose; __PACKAGE__->meta->make_immutable; 1;