File Coverage

blib/lib/Kossy.pm
Criterion Covered Total %
statement 147 175 84.0
branch 30 52 57.6
condition 9 19 47.3
subroutine 28 31 90.3
pod 6 7 85.7
total 220 284 77.4


line stmt bran cond sub pod time code
1             package Kossy;
2              
3 7     7   99449 use strict;
  7         13  
  7         152  
4 7     7   21 use warnings;
  7         8  
  7         116  
5 7     7   168 use 5.008004;
  7         22  
6 7     7   2771 use utf8;
  7         50  
  7         27  
7 7     7   212 use Carp qw//;
  7         10  
  7         88  
8 7     7   19 use Cwd qw//;
  7         7  
  7         70  
9 7     7   21 use File::Basename qw//;
  7         6  
  7         84  
10 7     7   3114 use Text::Xslate;
  7         48924  
  7         281  
11 7     7   3240 use HTML::FillInForm::Lite qw//;
  7         18141  
  7         136  
12 7     7   3464 use Try::Tiny;
  7         6691  
  7         287  
13 7     7   3211 use Encode;
  7         48383  
  7         374  
14 7     7   2576 use Router::Boom;
  7         18756  
  7         219  
15             use Class::Accessor::Lite (
16 7         28 new => 0,
17             rw => [qw/root_dir/]
18 7     7   32 );
  7         7  
19 7     7   327 use base qw/Exporter/;
  7         9  
  7         535  
20 7     7   2304 use Kossy::Exception;
  7         11  
  7         157  
21 7     7   2208 use Kossy::Connection;
  7         12  
  7         156  
22 7     7   2115 use Kossy::Request;
  7         19  
  7         182  
23 7     7   32 use Kossy::Response;
  7         9  
  7         115  
24 7     7   24 use HTTP::Headers::Fast;
  7         8  
  7         8045  
25              
26             our $VERSION = '0.40';
27             our @EXPORT = qw/new root_dir psgi build_app _router _connect get post router filter _wrap_filter/;
28              
29             our $XSLATE_CACHE = 1;
30             our $XSLATE_CACHE_DIR;
31             our $SECURITY_HEADER = 1;
32              
33             # cache underscore translation
34             HTTP::Headers::Fast::_standardize_field_name('X-Frame-Options');
35              
36             sub new {
37 5     5 1 663 my $class = shift;
38 5         5 my %args;
39 5 100       13 if ( @_ < 2 ) {
40 2         25 my $root_dir = shift;
41 2         6 my @caller = caller;
42 2   66     50 $root_dir ||= File::Basename::dirname( Cwd::realpath($caller[1]) );
43 2         5 $args{root_dir} = $root_dir;
44             }
45             else {
46 3         8 %args = @_;
47             }
48              
49 5         12 bless \%args, $class;
50             }
51              
52             sub psgi {
53 2     2 1 849 my $self = shift;
54 2 50       11 if ( ! ref $self ) {
55 2         3 my %args;
56 2 50       62 if ( @_ < 2 ) {
57 2         12 my $root_dir = shift;
58 2         6 my @caller = caller;
59 2   33     8 $root_dir ||= File::Basename::dirname( Cwd::realpath($caller[1]) );
60 2         6 $args{root_dir} = $root_dir;
61             }
62             else {
63 0         0 %args = @_;
64             }
65 2         7 $self = $self->new(%args);
66             }
67              
68 2         6 $self->build_app;
69             }
70              
71             sub build_app {
72 2     2 0 2 my $self = shift;
73              
74             #router
75 2         12 my $router = Router::Boom->new;
76 2         42 $router->add($_ => $self->_router->{$_} ) for keys %{$self->_router};
  2         4  
77 2         63 my $xslate_cache_local = $XSLATE_CACHE;
78 2         3 my $xslate_cache_dir_local = $XSLATE_CACHE_DIR;
79 2         2 my $security_header_local = $SECURITY_HEADER;
80 2         2 my %match_cache;
81              
82             #xslate
83 2         13 my $fif = HTML::FillInForm::Lite->new();
84             my $tx = Text::Xslate->new(
85             path => [ $self->root_dir . '/views' ],
86             input_layer => ':utf8',
87             module => ['Text::Xslate::Bridge::TT2Like','Number::Format' => [':subs']],
88             function => {
89             fillinform => sub {
90 0     0   0 my $q = shift;
91             return sub {
92 0         0 my ($html) = @_;
93 0         0 return Text::Xslate::mark_raw( $fif->fill( \$html, $q ) );
94             }
95 0         0 }
96             },
97 2 50       40 cache => $xslate_cache_local,
98             defined $xslate_cache_dir_local ? ( cache_dir => $xslate_cache_dir_local ) : (),
99             );
100              
101             sub {
102 18     18   43875 my $env = shift;
103 18         21 $Kossy::Response::SECURITY_HEADER = $security_header_local;
104             try {
105 18 100       327 my $header = bless {
106             'content-type' => 'text/html; charset=UTF-8',
107             $security_header_local ? ('x-frame-options' => 'DENY') : (),
108             }, 'HTTP::Headers::Fast';
109 18         77 my $c = Kossy::Connection->new({
110             tx => $tx,
111             req => Kossy::Request->new($env),
112             res => Kossy::Response->new(200, $header),
113             stash => {},
114             });
115 18         174 my $method = uc($env->{REQUEST_METHOD});
116 18         26 my $cache_key = $method . '-' . $env->{PATH_INFO};
117             my ($match,$args) = try {
118 18 100       293 if ( exists $match_cache{$cache_key} ) {
119 2         3 return @{$match_cache{$cache_key}};
  2         5  
120             }
121 16         72 my $path_info = Encode::decode_utf8( $env->{PATH_INFO}, Encode::FB_CROAK | Encode::LEAVE_SRC );
122 16         303 my @match = $router->match($path_info);
123 16 100       3410 if ( !@match ) {
124 2         7 $c->halt(404);
125             }
126            
127 14 100       33 if ( !exists $match[0]->{$method}) {
128 2         6 $c->halt(405);
129             }
130             $match_cache{$cache_key} = [$match[0]->{$method},$match[1]]
131 12 100       10 if ! scalar keys %{$match[1]};
  12         49  
132 12         29 return ($match[0]->{$method},$match[1]);
133             } catch {
134 4 50 33     39 if ( ref $_ && ref $_ eq 'Kossy::Exception' ) {
135 4         14 die $_; #rethrow
136             }
137 0         0 $c->halt(400,'unexpected character in request');
138 18         87 };
139            
140 14         173 my $code = $match->{__action__};
141 14   50     26 my $filters = $match->{__filter__} || [];
142 14 50 33     56 if ( $] == 5.020000 || $] == 5.020100 ) {
143             # workaround for 5.20.0 or 5.20.1 https://github.com/kazeburo/Kossy/pull/10
144 0         0 my %args = map { $_ => Encode::decode_utf8($args->{$_}) } keys %$args;
  0         0  
145 0         0 $c->args(\%args);
146             } else {
147 14         45 $c->args({%$args});
148             }
149             my $app = sub {
150 14         15 my ($self, $c) = @_;
151 14         10 my $response;
152 14         33 my $res = $code->($self, $c);
153 14 50       282 Carp::croak "Undefined Response" if ! defined $res;
154 14   100     39 my $res_t = ref($res) || '';
155 14 100       42 if ( $res_t eq 'Kossy::Response' ) {
    50          
    50          
    50          
156 1         2 $response = $res;
157             }
158             elsif ( $res_t eq 'Plack::Response' ) {
159 0         0 $response = bless $res, 'Kossy::Response';
160             }
161             elsif ( $res_t eq 'ARRAY' ) {
162 0         0 $response = Kossy::Response->new(@$res);
163             }
164             elsif ( !$res_t ) {
165 13         25 $c->res->body($res);
166 13         71 $response = $c->res;
167             }
168             else {
169 0         0 Carp::croak sprintf "Unknown Response: %s", $res_t;
170             }
171 14         49 $response;
172 14         85 };
173            
174 14         24 for my $filter ( reverse @$filters ) {
175 0         0 $app = $self->_wrap_filter($filter,$app);
176             }
177             # do all
178 14         10 local $Kossy::Response::DIRECT;
179 14         21 $app->($self, $c)->finalize;
180             } catch {
181 4 50 33     58 if ( ref $_ && ref $_ eq 'Kossy::Exception' ) {
182 4         9 return $_->response;
183             }
184 0         0 die $_;
185 18         106 };
186 2         39294 };
187             }
188              
189              
190              
191             my $_ROUTER={};
192             sub _router {
193 71     71   498 my $klass = shift;
194 71 100       84 my $class = ref $klass ? ref $klass : $klass;
195 71 100       95 if ( !$_ROUTER->{$class} ) {
196 3         4 $_ROUTER->{$class} = {};
197             }
198 71         150 $_ROUTER->{$class};
199             }
200              
201             sub _connect {
202 27     27   20 my $class = shift;
203 27         25 my ( $methods, $pattern, $filter, $code ) = @_;
204 27 50       34 $methods = ref($methods) ? $methods : [$methods];
205 27 50       36 if (!$code) {
206 27         18 $code = $filter;
207 27         28 $filter = [];
208             }
209 27         27 for my $method ( @$methods ) {
210 51         82 $class->_router->{$pattern}->{$method} = {
211             __action__ => $code,
212             __filter__ => $filter
213             };
214             }
215             }
216              
217             sub get {
218 21     21 1 1408 my $class = caller;
219 21         42 $class->_connect( ['GET','HEAD'], @_ );
220             }
221              
222             sub post {
223 3     3 1 12 my $class = caller;
224 3         10 $class->_connect( ['POST'], @_ );
225             }
226              
227             sub router {
228 3     3 1 12 my $class = caller;
229 3         6 $class->_connect( @_ );
230             }
231              
232             my $_FILTER={};
233             sub filter {
234 0     0 1   my $class = caller;
235 0 0         if ( !$_FILTER->{$class} ) {
236 0           $_FILTER->{$class} = {};
237             }
238 0 0         if ( @_ ) {
239 0           $_FILTER->{$class}->{$_[0]} = $_[1];
240             }
241 0           $_FILTER->{$class};
242             }
243              
244             sub _wrap_filter {
245 0     0     my $klass = shift;
246 0 0         my $class = ref $klass ? ref $klass : $klass;
247 0 0         if ( !$_FILTER->{$class} ) {
248 0           $_FILTER->{$class} = {};
249             }
250 0           my ($filter,$app) = @_;
251 0           my $filter_subref = $_FILTER->{$class}->{$filter};
252 0 0         Carp::croak sprintf("Filter:%s is not exists", $filter) unless $filter_subref;
253 0           return $filter_subref->($app);
254             }
255              
256             1;
257              
258             __END__