line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package IPC::Concurrency::DBI::Application; |
2
|
|
|
|
|
|
|
|
3
|
20
|
|
|
20
|
|
30499
|
use warnings; |
|
20
|
|
|
|
|
34
|
|
|
20
|
|
|
|
|
799
|
|
4
|
20
|
|
|
20
|
|
98
|
use strict; |
|
20
|
|
|
|
|
28
|
|
|
20
|
|
|
|
|
508
|
|
5
|
|
|
|
|
|
|
|
6
|
20
|
|
|
20
|
|
5900
|
use Data::Validate::Type; |
|
20
|
|
|
|
|
98263
|
|
|
20
|
|
|
|
|
1093
|
|
7
|
20
|
|
|
20
|
|
7552
|
use Data::Dumper; |
|
20
|
|
|
|
|
67582
|
|
|
20
|
|
|
|
|
1567
|
|
8
|
20
|
|
|
20
|
|
132
|
use Carp; |
|
20
|
|
|
|
|
31
|
|
|
20
|
|
|
|
|
2006
|
|
9
|
|
|
|
|
|
|
|
10
|
20
|
|
|
20
|
|
10274
|
use IPC::Concurrency::DBI::Application::Instance; |
|
20
|
|
|
|
|
51
|
|
|
20
|
|
|
|
|
17423
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 NAME |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
IPC::Concurrency::DBI::Application - Application identifier that represents the resource that will be limited. |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Version 1.2.0 |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=cut |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
our $VERSION = '1.2.0'; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
This module allows controlling how many instances of a given program are allowed |
30
|
|
|
|
|
|
|
to run in parallel. It does not manage forking or starting those instances. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
See the documentation of IPC::Concurrency::DBI for more information. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Configure the concurrency object. |
35
|
|
|
|
|
|
|
use IPC::Concurrency::DBI; |
36
|
|
|
|
|
|
|
my $concurrency_manager = IPC::Concurrency::DBI->new( |
37
|
|
|
|
|
|
|
'database_handle' => $dbh, |
38
|
|
|
|
|
|
|
'verbose' => 1, |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Retrieve the application. |
42
|
|
|
|
|
|
|
my $application = $concurrency_manager->get_application( |
43
|
|
|
|
|
|
|
name => 'cron_script.pl', |
44
|
|
|
|
|
|
|
); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Count how many instances are currently running. |
47
|
|
|
|
|
|
|
my $instances_count = $application->get_instances_count(); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# NOT IMPLEMENTED YET: Get a list of what instances are currently running. |
50
|
|
|
|
|
|
|
# my $instances = $application->get_instances_list() |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Start a new instance of the application. If this returns undef, we've |
53
|
|
|
|
|
|
|
# reached the limit. |
54
|
|
|
|
|
|
|
my $instance = $concurrent_program->start_instance(); |
55
|
|
|
|
|
|
|
unless ( defined( $instance ) ) |
56
|
|
|
|
|
|
|
{ |
57
|
|
|
|
|
|
|
print "Too many instances of $0 are already running.\n"; |
58
|
|
|
|
|
|
|
exit; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# [...] Do some work. |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# Now that the application is about to exit, flag the instance as completed. |
64
|
|
|
|
|
|
|
# (note: this is implicit when $instance is destroyed). |
65
|
|
|
|
|
|
|
$instance->finish(); |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 METHODS |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=head2 new() |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Create a new IPC::Concurrency::DBI::Application object. This function should |
73
|
|
|
|
|
|
|
not be called directly and its API could change, instead use |
74
|
|
|
|
|
|
|
IPC::Concurrency::DBI->get_application(). |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# Retrieve the application by name. |
77
|
|
|
|
|
|
|
my $application = IPC::Concurrency::DBI::Application->new( |
78
|
|
|
|
|
|
|
database_handle => $dbh, |
79
|
|
|
|
|
|
|
name => 'cron_script.pl', |
80
|
|
|
|
|
|
|
); |
81
|
|
|
|
|
|
|
die 'Application not found' |
82
|
|
|
|
|
|
|
unless defined( $application ); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Retrieve the application by ID. |
85
|
|
|
|
|
|
|
my $application = IPC::Concurrency::DBI::Application->new( |
86
|
|
|
|
|
|
|
database_handle => $dbh, |
87
|
|
|
|
|
|
|
id => 12345, |
88
|
|
|
|
|
|
|
); |
89
|
|
|
|
|
|
|
die 'Application not found' |
90
|
|
|
|
|
|
|
unless defined( $application ); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
'database handle': mandatory, a DBI object. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
'name': the name of the application to retrieve. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
'id': the internal ID of the application to retrieve. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=cut |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub new |
101
|
|
|
|
|
|
|
{ |
102
|
18
|
|
|
18
|
1
|
112692
|
my ( $class, %args ) = @_; |
103
|
18
|
|
|
|
|
46
|
my $database_handle = delete( $args{'database_handle'} ); |
104
|
18
|
|
|
|
|
36
|
my $name = delete( $args{'name'} ); |
105
|
18
|
|
|
|
|
36
|
my $application_id = delete( $args{'id'} ); |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Check parameters. |
108
|
18
|
100
|
|
|
|
89
|
croak "Argument 'database_handle' is required to create a new IPC::Concurrency::DBI::Application object" |
109
|
|
|
|
|
|
|
unless defined( $database_handle ); |
110
|
17
|
50
|
|
|
|
79
|
croak "Argument 'database_handle' is not a DBI object" |
111
|
|
|
|
|
|
|
if !Data::Validate::Type::is_instance( $database_handle, class => 'DBI::db' ); |
112
|
17
|
50
|
66
|
|
|
469
|
croak 'Cannot pass both a name and an application ID, please use only one' |
113
|
|
|
|
|
|
|
if defined( $name ) && defined( $application_id ); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Determine what key to use to retrieve the row. |
116
|
17
|
|
|
|
|
29
|
my ( $key, $value ); |
117
|
17
|
100
|
|
|
|
63
|
if ( defined( $name ) ) |
|
|
100
|
|
|
|
|
|
118
|
|
|
|
|
|
|
{ |
119
|
12
|
|
|
|
|
23
|
$key = 'name'; |
120
|
12
|
|
|
|
|
18
|
$value = $name; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
elsif ( defined( $application_id ) ) |
123
|
|
|
|
|
|
|
{ |
124
|
4
|
|
|
|
|
7
|
$key = 'ipc_concurrency_application_id'; |
125
|
4
|
|
|
|
|
7
|
$value = $application_id; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
else |
128
|
|
|
|
|
|
|
{ |
129
|
1
|
|
|
|
|
20
|
croak 'You need to specify either a name or an ID to retrieve an application'; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Retrieve the row from the database. |
133
|
16
|
|
|
|
|
248
|
my $data = $database_handle->selectrow_hashref( |
134
|
|
|
|
|
|
|
qq| |
135
|
|
|
|
|
|
|
SELECT * |
136
|
|
|
|
|
|
|
FROM ipc_concurrency_applications |
137
|
|
|
|
|
|
|
WHERE $key = ? |
138
|
|
|
|
|
|
|
|, |
139
|
|
|
|
|
|
|
{}, |
140
|
|
|
|
|
|
|
$value, |
141
|
|
|
|
|
|
|
); |
142
|
16
|
50
|
|
|
|
8226
|
croak 'Cannot execute SQL: ' . $database_handle->errstr() |
143
|
|
|
|
|
|
|
if defined( $database_handle->errstr() ); |
144
|
16
|
100
|
|
|
|
103
|
croak 'Application not found' |
145
|
|
|
|
|
|
|
unless defined( $data ); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Create the object. |
148
|
13
|
|
|
|
|
53
|
my $self = bless( |
149
|
|
|
|
|
|
|
{ |
150
|
|
|
|
|
|
|
database_handle => $database_handle, |
151
|
|
|
|
|
|
|
data => $data, |
152
|
|
|
|
|
|
|
}, |
153
|
|
|
|
|
|
|
$class, |
154
|
|
|
|
|
|
|
); |
155
|
|
|
|
|
|
|
|
156
|
13
|
|
|
|
|
64
|
return $self; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head2 start_instance() |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Start a new instance of the current application. |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
my $instance = $application->start_instance(); |
165
|
|
|
|
|
|
|
unless ( defined( $instance ) ) |
166
|
|
|
|
|
|
|
{ |
167
|
|
|
|
|
|
|
print "Too many instances of $0 are already running.\n"; |
168
|
|
|
|
|
|
|
exit; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=cut |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
sub start_instance |
174
|
|
|
|
|
|
|
{ |
175
|
5
|
|
|
5
|
1
|
1950
|
my ( $self ) = @_; |
176
|
5
|
|
|
|
|
18
|
my $database_handle = $self->get_database_handle(); |
177
|
5
|
|
|
|
|
16
|
my $maximum_instances = $self->get_maximum_instances(); |
178
|
|
|
|
|
|
|
|
179
|
5
|
|
|
|
|
27
|
my $rows_affected = $database_handle->do( |
180
|
|
|
|
|
|
|
q| |
181
|
|
|
|
|
|
|
UPDATE ipc_concurrency_applications |
182
|
|
|
|
|
|
|
SET current_instances = current_instances + 1, modified = ? |
183
|
|
|
|
|
|
|
WHERE ipc_concurrency_application_id = ? |
184
|
|
|
|
|
|
|
AND current_instances < maximum_instances |
185
|
|
|
|
|
|
|
|, |
186
|
|
|
|
|
|
|
{}, |
187
|
|
|
|
|
|
|
time(), |
188
|
|
|
|
|
|
|
$self->get_id(), |
189
|
|
|
|
|
|
|
); |
190
|
5
|
50
|
|
|
|
67736
|
croak 'Cannot execute SQL: ' . $database_handle->errstr() |
191
|
|
|
|
|
|
|
if defined( $database_handle->errstr() ); |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
# If no row was affected, we've reached the maximum number of instances or |
194
|
|
|
|
|
|
|
# the application ID has vanished. Either way, we can't start the instance. |
195
|
5
|
100
|
|
|
|
31
|
return unless $rows_affected == 1; |
196
|
|
|
|
|
|
|
|
197
|
4
|
|
|
|
|
49
|
return IPC::Concurrency::DBI::Application::Instance->new( |
198
|
|
|
|
|
|
|
application => $self, |
199
|
|
|
|
|
|
|
); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head1 GETTERS / SETTERS |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 get_instances_count() |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Retrieve the number of instances that currently running. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
my $instances_count = $application->get_instances_count(); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub get_instances_count |
214
|
|
|
|
|
|
|
{ |
215
|
5
|
|
|
5
|
1
|
2263
|
my ( $self ) = @_; |
216
|
5
|
|
|
|
|
19
|
my $database_handle = $self->get_database_handle(); |
217
|
5
|
|
|
|
|
13
|
my $maximum_instances = $self->get_maximum_instances(); |
218
|
|
|
|
|
|
|
|
219
|
5
|
|
|
|
|
26
|
my $data = $database_handle->selectrow_hashref( |
220
|
|
|
|
|
|
|
q| |
221
|
|
|
|
|
|
|
SELECT current_instances |
222
|
|
|
|
|
|
|
FROM ipc_concurrency_applications |
223
|
|
|
|
|
|
|
WHERE ipc_concurrency_application_id = ? |
224
|
|
|
|
|
|
|
|, |
225
|
|
|
|
|
|
|
{}, |
226
|
|
|
|
|
|
|
$self->get_id(), |
227
|
|
|
|
|
|
|
); |
228
|
5
|
50
|
|
|
|
1358
|
croak 'Cannot execute SQL: ' . $database_handle->errstr() |
229
|
|
|
|
|
|
|
if defined( $database_handle->errstr() ); |
230
|
5
|
50
|
|
|
|
15
|
croak 'Application not found' |
231
|
|
|
|
|
|
|
unless defined( $data ); |
232
|
|
|
|
|
|
|
|
233
|
5
|
|
|
|
|
37
|
return $data->{'current_instances'}; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head2 get_maximum_instances() |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Retrieve the maximum number of instances of the current application that are |
240
|
|
|
|
|
|
|
allowed to run in parallel. |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
my $maximum_instances = $application->get_maximum_instances(); |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=cut |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub get_maximum_instances |
247
|
|
|
|
|
|
|
{ |
248
|
13
|
|
|
13
|
1
|
864
|
my ( $self ) = @_; |
249
|
|
|
|
|
|
|
|
250
|
13
|
|
|
|
|
50
|
return $self->{'data'}->{'maximum_instances'}; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=head2 set_maximum_instances() |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Change the maximum number of instances of the current application that are |
257
|
|
|
|
|
|
|
allowed to run in parallel. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
$application->set_maximum_instances( 10 ); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=cut |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub set_maximum_instances |
264
|
|
|
|
|
|
|
{ |
265
|
1
|
|
|
1
|
1
|
20
|
my ( $self, $maximum_instances ) = @_; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Check parameters. |
268
|
1
|
50
|
|
|
|
7
|
croak 'The maximum number of instances needs to be a strictly positive integer' |
269
|
|
|
|
|
|
|
if !Data::Validate::Type::is_number( $maximum_instances, strictly_positive => 1 ); |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Update the application information. |
272
|
1
|
|
|
|
|
20
|
my $database_handle = $self->get_database_handle(); |
273
|
1
|
|
|
|
|
18
|
my $rows_affected = $database_handle->do( |
274
|
|
|
|
|
|
|
q| |
275
|
|
|
|
|
|
|
UPDATE ipc_concurrency_applications |
276
|
|
|
|
|
|
|
SET maximum_instances = ? |
277
|
|
|
|
|
|
|
WHERE ipc_concurrency_application_id = ? |
278
|
|
|
|
|
|
|
|, |
279
|
|
|
|
|
|
|
{}, |
280
|
|
|
|
|
|
|
$maximum_instances, |
281
|
|
|
|
|
|
|
$self->get_id(), |
282
|
|
|
|
|
|
|
); |
283
|
1
|
50
|
|
|
|
54665
|
croak 'Cannot execute SQL: ' . $database_handle->errstr() |
284
|
|
|
|
|
|
|
if defined( $database_handle->errstr() ); |
285
|
|
|
|
|
|
|
|
286
|
1
|
|
|
|
|
4
|
$self->{'data'}->{'maximum_instances'} = $maximum_instances; |
287
|
|
|
|
|
|
|
|
288
|
1
|
|
|
|
|
12
|
return 1; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head2 get_name() |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
Returns the name of the current application. |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
my $name = $application->get_name(); |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=cut |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
sub get_name |
301
|
|
|
|
|
|
|
{ |
302
|
1
|
|
|
1
|
1
|
300
|
my ( $self ) = @_; |
303
|
|
|
|
|
|
|
|
304
|
1
|
|
|
|
|
8
|
return $self->{'data'}->{'name'}; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=head2 get_id() |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Returns the internal ID of the current application. |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
my $application_id = $self->get_id(); |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=cut |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
sub get_id |
317
|
|
|
|
|
|
|
{ |
318
|
18
|
|
|
18
|
1
|
329
|
my ( $self ) = @_; |
319
|
|
|
|
|
|
|
|
320
|
18
|
|
|
|
|
156
|
return $self->{'data'}->{'ipc_concurrency_application_id'}; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=head2 get_database_handle() |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
Returns the database handle used for this object. |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
my $database_handle = $concurrency_manager->get_database_handle(); |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=cut |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub get_database_handle |
335
|
|
|
|
|
|
|
{ |
336
|
18
|
|
|
18
|
1
|
344
|
my ( $self ) = @_; |
337
|
|
|
|
|
|
|
|
338
|
18
|
|
|
|
|
52
|
return $self->{'database_handle'}; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=head1 BUGS |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
Please report any bugs or feature requests through the web interface at |
345
|
|
|
|
|
|
|
L. |
346
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
347
|
|
|
|
|
|
|
your bug as I make changes. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=head1 SUPPORT |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
perldoc IPC::Concurrency::DBI |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
You can also look for information at: |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=over 4 |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=item * GitHub's request tracker |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
L |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
L |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=item * CPAN Ratings |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
L |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item * MetaCPAN |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
L |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=back |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 AUTHOR |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
L, |
383
|
|
|
|
|
|
|
C<< >>. |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Copyright 2011-2017 Guillaume Aubert. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
This code is free software; you can redistribute it and/or modify it under the |
391
|
|
|
|
|
|
|
same terms as Perl 5 itself. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY |
394
|
|
|
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
395
|
|
|
|
|
|
|
PARTICULAR PURPOSE. See the LICENSE file for more details. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=cut |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
1; |