line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
use Kelp; |
3
|
2
|
|
|
2
|
|
2005
|
use Kelp::Base -strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
13
|
|
4
|
2
|
|
|
2
|
|
11
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
6
|
|
5
|
|
|
|
|
|
|
our @EXPORT = qw/ |
6
|
|
|
|
|
|
|
app |
7
|
|
|
|
|
|
|
attr |
8
|
|
|
|
|
|
|
config |
9
|
|
|
|
|
|
|
del |
10
|
|
|
|
|
|
|
debug |
11
|
|
|
|
|
|
|
error |
12
|
|
|
|
|
|
|
get |
13
|
|
|
|
|
|
|
module |
14
|
|
|
|
|
|
|
named |
15
|
|
|
|
|
|
|
param |
16
|
|
|
|
|
|
|
post |
17
|
|
|
|
|
|
|
put |
18
|
|
|
|
|
|
|
req |
19
|
|
|
|
|
|
|
res |
20
|
|
|
|
|
|
|
route |
21
|
|
|
|
|
|
|
run |
22
|
|
|
|
|
|
|
session |
23
|
|
|
|
|
|
|
stash |
24
|
|
|
|
|
|
|
template |
25
|
|
|
|
|
|
|
view |
26
|
|
|
|
|
|
|
/; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our $app; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
my $class = shift; |
31
|
|
|
|
|
|
|
my $caller = caller; |
32
|
2
|
|
|
2
|
|
10
|
no strict 'refs'; |
33
|
2
|
|
|
|
|
4
|
for my $sub (@EXPORT) { |
34
|
2
|
|
|
2
|
|
250
|
*{"${caller}::$sub"} = eval("\\\&$sub"); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1377
|
|
35
|
2
|
|
|
|
|
4
|
} |
36
|
40
|
|
|
|
|
1406
|
|
|
40
|
|
|
|
|
215
|
|
37
|
|
|
|
|
|
|
strict->import; |
38
|
|
|
|
|
|
|
warnings->import; |
39
|
2
|
|
|
|
|
11
|
feature->import(':5.10'); |
40
|
2
|
|
|
|
|
36
|
|
41
|
2
|
|
|
|
|
80
|
$app = Kelp->new(config_module => 'Config::Less', @_); |
42
|
|
|
|
|
|
|
$app->routes->base('main'); |
43
|
2
|
|
|
|
|
9
|
} |
44
|
2
|
|
|
|
|
5
|
|
45
|
|
|
|
|
|
|
my ( $path, $to ) = @_; |
46
|
|
|
|
|
|
|
$app->add_route( $path, $to ); |
47
|
|
|
|
|
|
|
} |
48
|
17
|
|
|
17
|
1
|
69
|
|
49
|
17
|
|
|
|
|
53
|
my ( $path, $to ) = @_; |
50
|
|
|
|
|
|
|
route ref($path) ? $path : [ GET => $path ], $to; |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
2
|
|
|
2
|
1
|
12
|
my ( $path, $to ) = @_; |
54
|
2
|
50
|
|
|
|
10
|
route ref($path) ? $path : [ POST => $path ], $to; |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
my ( $path, $to ) = @_; |
58
|
1
|
|
|
1
|
1
|
3
|
route ref($path) ? $path : [ PUT => $path ], $to; |
59
|
1
|
50
|
|
|
|
5
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
my ( $path, $to ) = @_; |
62
|
|
|
|
|
|
|
route ref($path) ? $path : [ DELETE => $path ], $to; |
63
|
1
|
|
|
1
|
1
|
3
|
} |
64
|
1
|
50
|
|
|
|
4
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# If we're running a test, then return the entire app, |
67
|
|
|
|
|
|
|
# otherwise return the PSGI subroutine |
68
|
1
|
|
|
1
|
1
|
3
|
return $ENV{KELP_TESTING} ? $app : $app->run; |
69
|
1
|
50
|
|
|
|
7
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
1; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=pod |
76
|
1
|
50
|
|
1
|
1
|
29
|
|
77
|
|
|
|
|
|
|
=head1 NAME |
78
|
|
|
|
|
|
|
|
79
|
3
|
|
|
3
|
1
|
21
|
Kelp::Less - Quick prototyping with Kelp |
80
|
2
|
|
|
2
|
1
|
7
|
|
81
|
2
|
|
|
2
|
1
|
11
|
=head1 SYNOPSIS |
82
|
0
|
|
|
0
|
0
|
0
|
|
83
|
2
|
|
|
2
|
1
|
9
|
use Kelp::Less; |
84
|
1
|
|
|
1
|
1
|
6
|
|
85
|
1
|
|
|
1
|
1
|
5
|
get '/person/:name' => sub { |
86
|
1
|
|
|
1
|
1
|
7
|
"Hello " . named 'name'; |
87
|
1
|
|
|
1
|
1
|
7
|
}; |
88
|
0
|
|
|
0
|
1
|
0
|
|
89
|
0
|
0
|
|
0
|
0
|
0
|
run; |
90
|
0
|
0
|
|
0
|
0
|
0
|
|
91
|
2
|
|
|
2
|
1
|
109
|
=head1 DESCRIPTION |
92
|
1
|
|
|
1
|
1
|
8
|
|
93
|
|
|
|
|
|
|
This class exists to provide a way for quick and sloppy prototyping of a web |
94
|
|
|
|
|
|
|
application. It is a wrapper for L<Kelp>, which imports several keywords, making |
95
|
|
|
|
|
|
|
it easier and less verbose to create a quick web app. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
It's called C<Less>, because there is less typing involved, and |
98
|
|
|
|
|
|
|
because it is suited for smaller, less complicated web projects. We encourage |
99
|
|
|
|
|
|
|
you to use it anywhere you see fit, however for mid-size and big applications we |
100
|
|
|
|
|
|
|
recommend that you use the fully structured L<Kelp>. This way you can take |
101
|
|
|
|
|
|
|
advantage of its powerful router, initialization and testing capabilities. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head1 QUICK START |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
Each web app begins with C<use Kelp::Less;>. This automatically imports C<strict>, |
106
|
|
|
|
|
|
|
C<warnings>, C<v5.10> as well as several useful functions. You can pass any |
107
|
|
|
|
|
|
|
parameters to the constructor at the C<use> statement: |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
use Kelp::Less mode => 'development'; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
The above is equivalent to: |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
use Kelp; |
114
|
|
|
|
|
|
|
my $app = Kelp->new( mode => 'development' ); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
After that, you could add any initializations and attributes. For example, connect |
117
|
|
|
|
|
|
|
to a database or setup cache. C<Kelp::Less> exports L<attr|Kelp::Base/attr>, |
118
|
|
|
|
|
|
|
so you can use it to register attributes to your app. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Connect to DBI and CHI right away |
121
|
|
|
|
|
|
|
attr dbh => sub { |
122
|
|
|
|
|
|
|
DBI->connect( @{ app->config('database') } ); |
123
|
|
|
|
|
|
|
}; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
attr cache => sub { |
126
|
|
|
|
|
|
|
CHI->new( @{ app->config('cache') } ); |
127
|
|
|
|
|
|
|
}; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Another lazy attribute. |
130
|
|
|
|
|
|
|
attr version => sub { |
131
|
|
|
|
|
|
|
app->dbh->selectrow_array("SELECT version FROM vars"); |
132
|
|
|
|
|
|
|
}; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# Later: |
135
|
|
|
|
|
|
|
app->dbh->do(...); |
136
|
|
|
|
|
|
|
app->cache->get(...); |
137
|
|
|
|
|
|
|
if ( app->version ) { ... } |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Now is a good time to add routes. Routes are added via the L</route> keyword and |
140
|
|
|
|
|
|
|
they are automatically registered in your app. A route needs two parameters - |
141
|
|
|
|
|
|
|
C<path> and C<destination>. These are exactly equivalent to L<Kelp::Routes/add>, |
142
|
|
|
|
|
|
|
and you are encouraged to read its POD to get familiar with how to define routes. |
143
|
|
|
|
|
|
|
Here are a few examples for the impatient: |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Add a 'catch-all-methods' route and send it to an anonymous sub |
146
|
|
|
|
|
|
|
route '/hello/:name' => sub { |
147
|
|
|
|
|
|
|
return "Hello " . named('name'); |
148
|
|
|
|
|
|
|
}; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# Add a POST route |
151
|
|
|
|
|
|
|
route [ POST => '/edit/:id' ] => sub { |
152
|
|
|
|
|
|
|
# Do something with named('id') |
153
|
|
|
|
|
|
|
}; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Route that runs an existing sub in your code |
156
|
|
|
|
|
|
|
route '/login' => 'login'; |
157
|
|
|
|
|
|
|
sub login { |
158
|
|
|
|
|
|
|
... |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Each route subroutine receives C<$self> and all named placeholders. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
route '/:id/:page' => sub { |
164
|
|
|
|
|
|
|
my ( $self, $id, $page ) = @_; |
165
|
|
|
|
|
|
|
}; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
Here, C<$self> is the app object and it can be used the same way as in a full |
168
|
|
|
|
|
|
|
L<Kelp> route. For the feeling of magic and eeriness, C<Kelp::Lite> aliases |
169
|
|
|
|
|
|
|
C<app> to C<$self>, so the former can be used as a full substitute to the |
170
|
|
|
|
|
|
|
latter. See the exported keywords section for more information. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
After you have added all of your routes, it is time to run the app. This is done |
173
|
|
|
|
|
|
|
via a single command: |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
run; |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
It returns PSGI ready subroutine, so you can immediately deploy your new app via |
178
|
|
|
|
|
|
|
Plack: |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
> plackup myapp.psgi |
181
|
|
|
|
|
|
|
HTTP::Server::PSGI: Accepting connections at http://0:5000/ |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head1 KEYWORDS |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
The following list of keywords are exported to allow for less typing in |
186
|
|
|
|
|
|
|
C<Kelp::Less>: |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head2 app |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
This a full alias for C<$self>. It is the application object, and an |
191
|
|
|
|
|
|
|
instance of the C<Kelp> class. You can use it for anything you would use |
192
|
|
|
|
|
|
|
C<$self> inside a route. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
route '/die' => sub { |
195
|
|
|
|
|
|
|
app->res->code(500); |
196
|
|
|
|
|
|
|
}; |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 attr |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Assigns lazy or active attributes (using L<Kelp::Base>) to C<app>. Use it to |
201
|
|
|
|
|
|
|
initialize your application. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
attr mongo => MongoDB::MongoClient->new( ... ); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 route |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Adds a route to C<app>. It is an alias to C<$self-E<gt>routes-E<gt>add>, and requires |
208
|
|
|
|
|
|
|
the exact same parameters. See L<Kelp::Routes> for reference. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
route '/get-it' => sub { "got it" }; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 get, post, put, del |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
These are shortcuts to C<route> restricted to the corresponding HTTP method. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
get '/data' => sub { "Only works with GET" }; |
217
|
|
|
|
|
|
|
post '/data' => sub { "Only works with POST" }; |
218
|
|
|
|
|
|
|
put '/data' => sub { "Only works with PUT" }; |
219
|
|
|
|
|
|
|
del '/data' => sub { "Only works with DELETE" }; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 param |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
An alias for C<$self-E<gt>param> that gets the GET or POST parameters. |
224
|
|
|
|
|
|
|
When used with no arguments, it will return an array with the names of all http |
225
|
|
|
|
|
|
|
parameters. Otherwise, it will return the value of the requested http parameter. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
get '/names' => sub { |
228
|
|
|
|
|
|
|
my @names = param; |
229
|
|
|
|
|
|
|
# Now @names contains the names of the params |
230
|
|
|
|
|
|
|
}; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
get '/value' => sub { |
233
|
|
|
|
|
|
|
my $id = param 'id'; |
234
|
|
|
|
|
|
|
# Now $is contains the value of 'id' |
235
|
|
|
|
|
|
|
}; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head2 stash |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
An alias for C<$self-E<gt>stash>. The stash is a concept originally conceived by the |
240
|
|
|
|
|
|
|
developers of L<Catalyst>. It's a hash that you can use to pass data from one |
241
|
|
|
|
|
|
|
route to another. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Create a bridge route that checks if the user is authenticated, and saves |
244
|
|
|
|
|
|
|
# the username in the stash. |
245
|
|
|
|
|
|
|
get '/user' => { bridge => 1, to => sub { |
246
|
|
|
|
|
|
|
return stash->{username} = app->authenticate(); |
247
|
|
|
|
|
|
|
}}; |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# This route is run after the above bridge, so we know that we have an |
250
|
|
|
|
|
|
|
# authenticated user and their username in the stash. |
251
|
|
|
|
|
|
|
get '/user/welcome' => sub { |
252
|
|
|
|
|
|
|
return "Hello " . stash 'username'; |
253
|
|
|
|
|
|
|
}; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
With no arguments C<stash> returns the entire stash hash. A single argument is |
256
|
|
|
|
|
|
|
interpreted as the key to the stash hash and its value is returned accordingly. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=head2 named |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
An alias for C<$self-E<gt>named>. The C<named> hash contains the names and values of |
261
|
|
|
|
|
|
|
the named placeholders from the current route's path. Much like the C<stash>, |
262
|
|
|
|
|
|
|
with no arguments it returns the entire C<named> hash, and with a single |
263
|
|
|
|
|
|
|
argument it returns the value for the corresponding key in the hash. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
get '/:name/:id' => sub { |
266
|
|
|
|
|
|
|
my $name = named 'name'; |
267
|
|
|
|
|
|
|
my $id = name 'id'; |
268
|
|
|
|
|
|
|
}; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
In the above example a GET request to C</james/1000> will initialize C<$name> |
271
|
|
|
|
|
|
|
with C<"james"> and C<$id> with C<1000>. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head2 req |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
An alias for C<$self-E<gt>req>, this provides quick access to the |
276
|
|
|
|
|
|
|
L<Kelp::Request> object for the current route. |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Inside a route |
279
|
|
|
|
|
|
|
if ( req->is_ajax ) { |
280
|
|
|
|
|
|
|
... |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head2 res |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
An alias for C<$self-E<gt>res>, this is a shortcut for the L<Kelp::Response> |
286
|
|
|
|
|
|
|
object for the current route. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Inside a route |
289
|
|
|
|
|
|
|
res->code(403); |
290
|
|
|
|
|
|
|
res->json->render({ message => "Forbidden" }); |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head2 template |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
A shortcut to C<$self-E<gt>res-E<gt>template>. Renders a template using the |
295
|
|
|
|
|
|
|
currently loaded template module. Note that a Kelp::Less application does not |
296
|
|
|
|
|
|
|
by default load a template module, so you will have to load it yourself. |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
use Kelp::Less; |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
module 'Template', path => 'views'; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
get '/hello/:name' => sub { |
303
|
|
|
|
|
|
|
template 'hello.tt', { name => named 'name' }; |
304
|
|
|
|
|
|
|
}; |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=head2 view |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
A shortcut for L</template>. |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
get '/hello/:name' => sub { |
311
|
|
|
|
|
|
|
view 'hello.tt', { name => named 'name' }; |
312
|
|
|
|
|
|
|
}; |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head2 run |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
Creates and returns a PSGI ready subroutine, and makes the app ready for C<Plack>. |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 module |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Loads a Kelp module. The module options may be specified after the module name. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
module 'JSON::XS', pretty => 1; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=head2 config |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
Provides procedural interface to the configuration. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
get '/hello' => sub { |
329
|
|
|
|
|
|
|
my $baz = config('bar.foo.baz'); |
330
|
|
|
|
|
|
|
}; |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=head1 TESTING |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
When writing a C<Kelp::Less> app, we don't have a separate class to initialize and |
335
|
|
|
|
|
|
|
feed into a L<Kelp::Test> object, because all of our code is contained in the |
336
|
|
|
|
|
|
|
C<app.psgi> file. In this case, the C<Kelp::Test> object can be initialized |
337
|
|
|
|
|
|
|
with the name of the C<PSGI> file in the C<psgi> argument. |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
# t/main.t |
340
|
|
|
|
|
|
|
use Kelp::Test; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
my $t = Kelp::Test->new( psgi => 'app.psgi' ); |
343
|
|
|
|
|
|
|
# Do some tests ... |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
Since you don't have control over the creation of the C<Kelp> object, if you |
346
|
|
|
|
|
|
|
need to specify a different mode for testing, you can use the C<PLACK_ENV> |
347
|
|
|
|
|
|
|
environmental variable: |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
> PLACK_ENV=test prove -l |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
This will enable the C<conf/test.pl> configuration, which you should |
352
|
|
|
|
|
|
|
tailor to your testing needs. |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
This module's interface was inspired by L<Dancer>, which in its turn was |
357
|
|
|
|
|
|
|
inspired by Sinatra, so Viva La Open Source! |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=cut |