File Coverage

blib/lib/Cot.pm
Criterion Covered Total %
statement 67 195 34.3
branch 4 36 11.1
condition 0 14 0.0
subroutine 20 45 44.4
pod 8 10 80.0
total 99 300 33.0


line stmt bran cond sub pod time code
1             package Cot;
2              
3 1     1   967 use strict;
  1         3  
  1         49  
4 1     1   7 use warnings;
  1         2  
  1         38  
5 1     1   41 use 5.008005;
  1         4  
  1         73  
6             our $VERSION = "0.11";
7             $VERSION = eval $VERSION;
8 1     1   6 use File::Spec;
  1         1  
  1         41  
9 1     1   1063 use Plack::Request;
  1         110943  
  1         28  
10 1     1   829 use Plack::Runner;
  1         8629  
  1         45  
11 1     1   1143 use Plack::App::File;
  1         33803  
  1         33  
12 1     1   2340 use DirHandle;
  1         486  
  1         30  
13 1     1   5 use Carp;
  1         2  
  1         45  
14 1     1   5 use vars qw($AUTOLOAD %POOL);
  1         2  
  1         87  
15              
16             sub import {
17 1     1   14 my $class = shift;
18 1         4 my $pkg = caller(0);
19 1         16 strict->import;
20 1         24 warnings->import;
21             {
22 1     1   4 no strict 'refs';
  1         2  
  1         65  
  1         3  
23 1         3 push @{"$pkg\::ISA"}, $class;
  1         24  
24             }
25 1         4 for my $func (qw/run get post put patch delete options any static indexes plugin/) {
26 1     1   5 no strict 'refs';
  1         1  
  1         2553  
27 11         16 *{"$pkg\::$func"} = \&{"_export_$func"};
  11         62  
  11         33  
28             }
29             }
30              
31             sub _register_plugin {
32 1     1   2 my ( $class, $plugin_klass ) = @_;
33 1         6 $plugin_klass->new->init( $class->_app );
34             }
35              
36             sub _root {
37 2 50   2   33 $ENV{COT_ROOT} || '.';
38             }
39              
40             sub _app {
41 1     1   3 my $class = shift;
42 1 50       6 $POOL{ $class->_root } || $class->new;
43             }
44              
45             sub new {
46 1     1 0 7 my $class = shift;
47 1         5 my $self = bless {
48             controller => {},
49             plugins => [],
50             }, $class;
51 1         5 $POOL{ $class->_root } = $self;
52             }
53              
54             # get '/' => sub { my $c = shift; }
55             sub _method {
56 0     0   0 my ( $class, $path, $sub, @methods ) = @_;
57 0         0 foreach (@methods) {
58 0   0     0 $class->_app->{controller}->{$_} ||= {};
59 0         0 $class->_app->{controller}->{$_}->{$path} = $sub;
60             }
61             }
62              
63             sub _export_plugin {
64 0     0   0 my @plugins = @_;
65 0         0 my $class = caller(0);
66 0         0 foreach (@_) {
67 0         0 my $klass = "Cot::Plugin::$_";
68 0 0       0 eval "require $klass" or croak "Plugin[$_] is not installed.";
69 0         0 $klass->_regist($class);
70             }
71             }
72              
73             sub _export_get {
74 0     0   0 my ( $path, $sub ) = @_;
75 0         0 my $class = caller(0);
76 0         0 $class->_method( $path, $sub, 'get' );
77             }
78              
79             sub _export_post {
80 0     0   0 my ( $path, $sub ) = @_;
81 0         0 my $class = caller(0);
82 0         0 $class->_method( $path, $sub, 'post' );
83             }
84              
85             sub _export_put {
86 0     0   0 my ( $path, $sub ) = @_;
87 0         0 my $class = caller(0);
88 0         0 $class->_method( $path, $sub, 'put' );
89             }
90              
91             sub _export_delete {
92 0     0   0 my ( $path, $sub ) = @_;
93 0         0 my $class = caller(0);
94 0         0 $class->_method( $path, $sub, 'delete' );
95             }
96              
97             sub _export_patch {
98 0     0   0 my ( $path, $sub ) = @_;
99 0         0 my $class = caller(0);
100 0         0 $class->_method( $path, $sub, 'patch' );
101             }
102              
103             sub _export_options {
104 0     0   0 my ( $path, $sub ) = @_;
105 0         0 my $class = caller(0);
106 0         0 $class->_method( $path, $sub, 'options' );
107             }
108              
109             sub _export_any {
110 0     0   0 my ( $path, $sub ) = @_;
111 0         0 my $class = caller(0);
112 0         0 $class->_method( $path, $sub, 'get', 'post', 'put', 'patch', 'options',
113             'delete' );
114             }
115              
116             sub _export_static {
117 0     0   0 my ($path) = @_;
118 0         0 my $class = caller(0);
119 0         0 my $controller = $class->_app->{controller};
120 0   0     0 $controller->{get} ||= {};
121 0         0 $controller->{get}->{$path} = \&_static;
122             }
123              
124             sub _export_indexes {
125 0     0   0 my ($path) = @_;
126 0         0 my $class = caller(0);
127 0         0 my $controller = $class->_app->{controller};
128 0   0     0 $controller->{get} ||= {};
129 0         0 $controller->{get}->{$path} = \&_indexes;
130             }
131             sub _indexes {
132 0     0   0 my $self = shift;
133 0         0 my $path_info = $self->env->{PATH_INFO};
134 0   0     0 my $path =
135             File::Spec->catfile( $ENV{DOCUMENT_ROOT} || 'public', $path_info );
136 0 0       0 if ( !-e $path ) {
    0          
137 0         0 $self->notfound_response;
138             }
139             elsif ( -d $path ) {
140 0 0       0 if ( $path_info =~ /.*\/$/ ) {
141 0         0 my $body = '
    ';
142 0         0 my $dh = DirHandle->new($path);
143 0         0 while (defined($_ = $dh->read)) { $body .= sprintf('
  • %s
  • ', $_,$_); }
      0         0  
    144 0         0 $body .= '';
    145 0         0 $self->res->status(200);
    146 0         0 $self->res->headers({'Content-Type' => 'text/html', charset => 'utf-8'});
    147 0         0 $self->res->body($body);
    148 0         0 return;
    149             }
    150             else {
    151 0         0 $self->redirect_response( $path_info . '/' );
    152             }
    153             }
    154             else {
    155 0         0 my $file = Plack::App::File->new( file => $path )->call( $self->env );
    156 0         0 $self->res->status( $file->[0] );
    157 0         0 $self->res->headers( $file->[1] );
    158 0         0 $self->res->body( $file->[2] );
    159             }
    160              
    161             }
    162             sub _static {
    163 0     0   0 my $self = shift;
    164 0         0 my $path_info = $self->env->{PATH_INFO};
    165 0   0     0 my $path =
    166             File::Spec->catfile( $ENV{DOCUMENT_ROOT} || 'public', $path_info );
    167 0 0       0 if ( !-e $path ) {
        0          
    168 0         0 $self->notfound_response;
    169             }
    170             elsif ( -d $path ) {
    171 0 0       0 if ( $path_info =~ /.*\/$/ ) {
    172 0   0     0 my @di = split( /:/, $ENV{COT_DIRECTORYINDEX} || '' );
    173 0         0 foreach my $di (@di) {
    174 0         0 my $index = File::Spec->catfile( $path, $di );
    175 0 0       0 if ( -f $index ) {
    176 0         0 my $file =
    177             Plack::App::File->new( file => $index )
    178             ->call( $self->env );
    179 0         0 $self->res->status( $file->[0] );
    180 0         0 $self->res->headers( $file->[1] );
    181 0         0 $self->res->body( $file->[2] );
    182 0         0 return;
    183             }
    184             }
    185 0         0 $self->forbidden_response;
    186             }
    187             else {
    188 0         0 $self->redirect_response( $path_info . '/' );
    189             }
    190             }
    191             else {
    192 0         0 my $file = Plack::App::File->new( file => $path )->call( $self->env );
    193 0         0 $self->res->status( $file->[0] );
    194 0         0 $self->res->headers( $file->[1] );
    195 0         0 $self->res->body( $file->[2] );
    196             }
    197              
    198             }
    199              
    200             sub app {
    201 0     0 0 0 my ( $class, $env ) = @_;
    202 0         0 my $self = $class->_app;
    203 0         0 my @path_info = ();
    204 0         0 my $req = Plack::Request->new($env);
    205 0         0 my $method = lc( $req->method );
    206 0         0 my $path = $req->uri->path;
    207 0 0       0 $path =~ s/$env->{SCRIPT_NAME}// if $env->{SCRIPT_NAME};
    208 0         0 my @path = File::Spec->splitdir($path);
    209 0   0     0 my $controllers = $self->{controller}->{$method} || {};
    210 0         0 my $controller;
    211              
    212 0         0 for ( ; ; ) {
    213 0         0 my $u = File::Spec->catdir(@path);
    214 0 0       0 $controller = $controllers->{$u} and last;
    215 0 0       0 last unless scalar(@path);
    216 0         0 unshift @path_info, pop(@path);
    217             }
    218 0         0 $self->{req} = $req;
    219 0         0 $self->{env} = $env;
    220 0         0 $self->{res} = $req->new_response;
    221 0         0 $self->{path} = $path;
    222 0         0 $self->{path_info} = \@path_info;
    223 0 0       0 $controller ? &{ \&$controller }($self) : $self->forbidden_response;
      0         0  
    224 0         0 $self->res->finalize;
    225             }
    226 0     0 1 0 sub req { shift->{req}; }
    227 0     0 1 0 sub res { shift->{res}; }
    228 0     0 1 0 sub env { shift->{env}; }
    229 0     0 1 0 sub path { shift->{path}; }
    230 0     0 1 0 sub path_info { shift->{path_info}; }
    231              
    232             sub forbidden_response {
    233 0     0 1 0 my $self = shift;
    234 0         0 $self->res->status(403);
    235 0         0 $self->res->body('forbidden');
    236             }
    237              
    238             sub notfound_response {
    239 0     0 1 0 my $self = shift;
    240 0         0 $self->res->status(404);
    241 0         0 $self->res->body('not found');
    242             }
    243              
    244             sub redirect_response {
    245 0     0 1 0 my ( $self, $url ) = @_;
    246 0         0 $self->res->redirect($url);
    247             }
    248              
    249             sub AUTOLOAD {
    250 1     1   3 my $self = shift;
    251 1         3 my $caller = caller(0);
    252 1         8 ( my $method = $AUTOLOAD ) =~ s/.*:://;
    253 1 50       10 croak(" App can be extended only by Plugins [ !$caller->$method ] ")
    254             unless ( $caller->isa('Cot::Plugin') );
    255 1     1   13 no strict 'refs';
      1         12  
      1         278  
    256             *$method = sub {
    257 1     1   2 my $self = shift;
    258 1 50       4 $self->{$method} = $_[0] if ( $_[0] );
    259 1         24 return $self->{$method};
    260 1         10 };
    261 1         6 $self->$method(@_);
    262             }
    263              
    264 0     0     sub DESTROY { }
    265              
    266             sub _export_run {
    267 0     0     my @argv = @_;
    268 0           my $class = caller(0);
    269 0           my $runner = Plack::Runner->new;
    270 0 0         if ( scalar @argv ) {
    271 0 0         @argv = split( /\s+/, $argv[0] ) if ( scalar @argv == 1 );
    272 0           $runner->parse_options(@argv);
    273             }
    274 0     0     my $app = sub { $class->app(shift); };
      0            
    275 0           $runner->run($app);
    276             }
    277              
    278             1;
    279             __END__