line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Articulate; |
2
|
4
|
|
|
4
|
|
19326
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
150
|
|
3
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
112
|
|
4
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
2168
|
use Moo; |
|
4
|
|
|
|
|
62443
|
|
|
4
|
|
|
|
|
22
|
|
6
|
|
|
|
|
|
|
with 'MooX::Singleton'; |
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
8406
|
use Articulate::Service; |
|
4
|
|
|
|
|
13
|
|
|
4
|
|
|
|
|
19
|
|
9
|
4
|
|
|
4
|
|
1395
|
use Module::Load (); |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
1474
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.003'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Articulate - A lightweight Perl CMS Framework |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 WARNING |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
This is very much in alpha. Things will change. Feel free to build things and have fun, but don't hook up anything business-critical just yet! |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 SYNOPSIS |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# (in bin/app.pl) |
23
|
|
|
|
|
|
|
use Dancer; |
24
|
|
|
|
|
|
|
use Dancer::Plugin::Articulate; |
25
|
|
|
|
|
|
|
articulate_app->enable; |
26
|
|
|
|
|
|
|
dance; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
B provides a content management service for your web app. It's lightweight, i.e. it places minimal demands on your app while maximising 'whipuptitude': it gives you a single interface in code to a framework that's totally modular underneath, and it won't claim any URL endpoints for itself. |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
You don't need to redesign your app around Articulate, it's a service that you call on when you need it, and all the 'moving parts' can be switched out if you want to do things your way. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
It's written in Perl, the fast, reliable 'glue language' that's perfect for agile web development projects, and currently runs on the L and L web frameworks. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 GETTING STARTED |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Don't forget to install Articulate - remember, it's a library, not an app. |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# From source: |
39
|
|
|
|
|
|
|
perl Makefile.PL |
40
|
|
|
|
|
|
|
make |
41
|
|
|
|
|
|
|
make test |
42
|
|
|
|
|
|
|
make install |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
Check out the examples in the C folder of the distribution. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
To add Articulate to your own app, you'll need to: |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
=over |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=item * add it to your C or similar (see example above) using either L or L. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=item * edit your C to configure the components you want (check each of the components for a description of their config options - or just borrow a config from one of the examples) |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=item * write any custom code you want if you don't like what's there, and just swap it out in the config. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=item * polish off your front-end, all the backend is taken care of! |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=back |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Curious about how it all fits together? Read on... |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head1 DESCRIPTION |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Articulate is a set of components that work together to provide a content management service that will sit alongside an existing Dancer app or form the basis of a new one. |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
If you want to see one in action, grab the source and run: |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# If you have Dancer and Dancer::Plugin::Articulate installed: |
69
|
|
|
|
|
|
|
cd examples/plain-speaking |
70
|
|
|
|
|
|
|
perl bin/app.pl -e dancer1 |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Or, if you have Dancer2 and Dancer2::Plugin::Articulate installed: |
73
|
|
|
|
|
|
|
cd examples/plain-speaking |
74
|
|
|
|
|
|
|
perl bin/app.psgi |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
You can see how it's configured by looking at |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
examples/plain-speaking/config.yml |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Notice that C doesn't directly load anything but the Articulate plugin (which loads config into this module). Everything you need is in C, and you can replace components with ones you've written if your app needs to do different things. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 Request/Response lifecycle summary |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
In a B, you parse user input and pick the parameters you want to send to the B. Have a look at L for some examples. The intention is that routes are as 'thin' as possible: business logic should all be done by some part of the service and not in the route handler. The route handler maps endpoints (URLs) to service requests; structured responses are passed back as return values and are picked up by the B. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
B pass B to B. A B contains a B (like C) and B (like the B you want to create it at and the B you want to place there). See L for more details. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
The B is responsible for handling requests for managed content. L B to a service B, asking each in turn if they are willing to handle the request (normally the provider will dertermine this based on the request verb). A provider typically checks a user has suitable permission, then interacts with the content storage system. |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
B is controlled by L. It delegates to a storage class which is configured for actions like C and C. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Content is stored in a structure called an B- (see L), which has a C (see L), the B (which could be a binary blob like an image, plain text, markdown, XML, etc.) and the associated B or B (which is a hashref).
|
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Before items can be placed in storage, the service should take care to run them through B. L delegates this to validators, and if there are any applicable validators, they will check if the content is valid. The content may also be B, i.e. further metadata added, like the time at which the request was made (consult L for details). |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
After items are retrieved from storage, there is the opportunity to B them, for instance by including relevant content from elsewhere which belongs in the response. See L for details on this. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
If at any time uncaught errors are thrown, including recognised L objects, they are caught and handled by L. L should therefore always return an L object. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Once the request finds it back to the Route it will typically be B immediately (see L), and the resulting response passed back to the user. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 Components |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
The following classes are persistent, configurable B of the system: |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=over |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=item * L |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=item * L |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item * L |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=item * L |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item * L |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=item * L |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=item * L |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=item * L |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=item * L |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item * L |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=back |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=head2 Data Classes |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
The following classes are used for passing request data between components: |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=over |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item * L |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item * L |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=item * L |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item * L |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=item * L |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=item * L |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=item * L |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item * L |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item * L |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=back |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head2 Other modules of interest |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=over |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=item * L |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=item * L |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=item * L |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=item * L |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=item * L |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=back |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 Instantiation |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Articulate provides a very handy way of creating (or B) objects through your config. The following config, for instance, assignes to the providers attribute (on some other object), an arrayref of four objects, the first created without no arguments, two created with arguments, and a final one created without arguments but using an unusual constructor. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
providers: |
177
|
|
|
|
|
|
|
- MyProvider::Simple |
178
|
|
|
|
|
|
|
- class: MyProvider::Congfigurable |
179
|
|
|
|
|
|
|
args: |
180
|
|
|
|
|
|
|
verbose: 1 |
181
|
|
|
|
|
|
|
- MyProvider::Congfigurable: |
182
|
|
|
|
|
|
|
lax: 1 |
183
|
|
|
|
|
|
|
verbose: 1 |
184
|
|
|
|
|
|
|
- class: MyProvider::Idiosyncratic |
185
|
|
|
|
|
|
|
constuctor: tada |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
For more details, see L. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head2 Delegation |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
A key part of the flexibility of Articulate is that objects often B functions to other objects (the B) |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
Typically, one class delegates to a series of providers, which are each passed the same arguments in turn. L is a good example of this. Sometimes the response from one provider will halt the delegation - see L for an example of this. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
Occasionally, only one provider is possible, for instance L. In this case there is a substitution rather than a delegation. |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=cut |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head1 METHOD |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head3 enable |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Sets up the routes. This does not happen at construction so you can control the point at which routes are declared. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head3 enabled |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
Please do not set this directly, use C instead. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=head3 routes |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
The packages which provide routes for Articulate. See L and L for more details. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=head3 components |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
The different working pieces of the Articulate app. Components all have access to each other indirectly and they provide features across Articulate; see L for more details. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=cut |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub enable { |
222
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
223
|
0
|
|
|
|
|
|
foreach my $route (@{ $self->routes }){ |
|
0
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
|
$route->app($self); |
225
|
0
|
|
|
|
|
|
$route->enable; |
226
|
|
|
|
|
|
|
} |
227
|
0
|
|
|
|
|
|
$self->enabled(1); |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
has enabled => |
231
|
|
|
|
|
|
|
is => 'rw', |
232
|
|
|
|
|
|
|
default => sub { 0 }; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
has routes => ( |
235
|
|
|
|
|
|
|
is => 'rw', |
236
|
|
|
|
|
|
|
default => sub { [] }, |
237
|
|
|
|
|
|
|
coerce => sub { Module::Load::load('Articulate::Syntax'); Articulate::Syntax::instantiate_array ( @_ ) }, |
238
|
|
|
|
|
|
|
trigger => sub { |
239
|
|
|
|
|
|
|
my $self = shift; |
240
|
|
|
|
|
|
|
my $orig = shift; |
241
|
|
|
|
|
|
|
$_->app($self) foreach @$orig; |
242
|
|
|
|
|
|
|
}, |
243
|
|
|
|
|
|
|
); |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
has components => ( |
246
|
|
|
|
|
|
|
is => 'rw', |
247
|
|
|
|
|
|
|
default => sub { {} }, |
248
|
|
|
|
|
|
|
coerce => sub { |
249
|
|
|
|
|
|
|
my $orig = shift; |
250
|
|
|
|
|
|
|
Module::Load::load('Articulate::Syntax'); |
251
|
|
|
|
|
|
|
Articulate::Syntax::instantiate_selection ( $orig ); |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
}, |
254
|
|
|
|
|
|
|
trigger => sub { |
255
|
|
|
|
|
|
|
my $self = shift; |
256
|
|
|
|
|
|
|
my $orig = shift; |
257
|
|
|
|
|
|
|
$orig->{$_}->app($self) foreach keys %$orig; |
258
|
|
|
|
|
|
|
}, |
259
|
|
|
|
|
|
|
); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head1 BUGS |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
Bugs should be reported to the L. Pull Requests welcome! |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=head1 COPYRIGHT |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
Articulate is Copyright 2014-2015 Daniel Perrett. You are free to use it subject to the same terms as perl: see the LICENSE file included in this distribution for what this means. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Currently Articulate is bundled with versions of other software whose license information you can access from the LICENSE file. |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=cut |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
1; |