File Coverage

blib/lib/MongoDBx/Tiny.pm
Criterion Covered Total %
statement 83 253 32.8
branch 6 74 8.1
condition 0 47 0.0
subroutine 24 45 53.3
pod 26 29 89.6
total 139 448 31.0


line stmt bran cond sub pod time code
1             package MongoDBx::Tiny;
2              
3 3     3   119805 use 5.006;
  3         7  
4 3     3   9 use strict;
  3         4  
  3         46  
5 3     3   9 use warnings;
  3         6  
  3         119  
6              
7             =head1 NAME
8              
9             MongoDBx::Tiny - Simple Mongo ORM for Perl
10              
11             =head1 VERSION
12              
13             Version 0.02_03
14              
15             =cut
16              
17             our $VERSION = '0.02_03';
18              
19             =head1 SYNOPSIS
20              
21             # --------------------
22             package My::Data;
23              
24             use MongoDBx::Tiny;
25              
26             CONNECT_INFO host => 'localhost', port => 27017;
27             DATABASE_NAME 'my_data';
28              
29             # --------------------
30             package My::Data::Foo;
31              
32             use MongoDBx::Tiny::Document;
33              
34             COLLECTION_NAME 'foo';
35              
36             ESSENTIAL qw/code/;
37             FIELD 'code', INT, LENGTH(10), DEFAULT('0'), REQUIRED;
38             FIELD 'name', STR, LENGTH(30), DEFAULT('noname');
39              
40             # --------------------
41             package main;
42              
43             my $tiny = My::Data->new;
44             $tiny->insert(foo => { code => 123, name => "foo_123"}) or die $!;
45             my $foo = $tiny->single(foo => { code => 123});
46             $foo->name('foo_321');
47             $foo->update;
48             $foo->remove;
49              
50             =cut
51              
52 3     3   1129 use MongoDB;
  3         3423222  
  3         92  
53 3     3   22 use Carp qw/carp confess/;
  3         4  
  3         114  
54 3     3   1073 use Data::Dumper;
  3         8830  
  3         133  
55 3     3   1165 use MongoDBx::Tiny::Util;
  3         5  
  3         168  
56 3     3   1304 use Params::Validate ();
  3         6227  
  3         72  
57 3     3   15 use Scalar::Util qw(blessed);
  3         4  
  3         153  
58              
59              
60             sub import {
61 3     3   32 my $caller = (caller(0))[0];
62             {
63 3     3   12 no strict 'refs';
  3         2  
  3         219  
  3         3  
64 3         5 push @{"${caller}::ISA"}, __PACKAGE__;
  3         39  
65             }
66 3         11 strict->import;
67 3         19 warnings->import;
68 3         265 __PACKAGE__->export_to_level(1, @_);
69             }
70              
71             require Exporter;
72 3     3   11 use base qw/Exporter/;
  3         4  
  3         303  
73             our @EXPORT = qw/CONNECT_INFO DATABASE_NAME LOAD_PLUGIN/;
74             our $_CONNECT_INFO;
75             our $_DATABASE_NAME;
76              
77             =head1 EXPORT
78              
79             A list of functions that can be exported.
80              
81             =head2 CONNECT_INFO
82              
83             CONNECT_INFO host => 'localhost', port => 27017;
84              
85             =head2 DATABASE_NAME
86              
87             DATABASE_NAME 'my_data';
88              
89             =head2 LOAD_PLUGIN
90              
91             LOAD_PLUGIN 'One';
92             LOAD_PLUGIN 'Two';
93              
94             =cut
95              
96             {
97 3     3   27 no warnings qw(once);
  3         3  
  3         1499  
98             *CONNECT_INFO = \&install_connect_info;
99             *DATABASE_NAME = \&install_database_name;
100             *LOAD_PLUGIN = \&install_plugin;
101             }
102              
103 1     1 0 5 sub install_connect_info { util_class_attr('CONNECT_INFO', @_) }
104              
105 1     1 0 4 sub install_database_name { util_class_attr('DATABASE_NAME',@_) }
106              
107             sub install_plugin {
108 1     1 0 5 my $class = (caller(0))[0];
109 1         6 $class->load_plugin(shift)
110             }
111              
112              
113             =head1 SUBROUTINES/METHODS
114              
115             =head2 new
116              
117             my $tiny = My::Data->new();
118              
119             # or you can specify connect_info, database_name.
120             my $tiny = My::Data->new({
121             connect_info => [ host => 'localhost', port => 27017 ],
122             database_name => 'my_data',
123             });
124              
125             =cut
126              
127             sub new {
128 1     1 1 92 my $class = shift;
129 1         3 my $opt = shift;
130 1 50       5 if (ref $opt->{connect_info} eq 'ARRAY') {
131 0         0 $class->install_connect_info($opt->{connect_info});
132             }
133 1 50       3 if ($opt->{database_name} ) {
134 0         0 $class->install_database_name($opt->{database_name});
135             }
136 1         3 my $self = bless{},$class;
137 1         2 eval { $self->connect; };
  1         6  
138              
139 1 50       4 if ($@) {
140 1         308 Carp::carp $@;
141 1         7 return;
142             }
143              
144 0         0 return $self;
145             }
146              
147             =head2 get_connection
148              
149             returns MongoDB::Connection. you can override how to get connection object.
150              
151             sub get_connection {
152             my $class = shift;
153              
154             return MongoDB::Connection->new(@{$class->CONNECT_INFO}) if !$ENV{PLACK_ENV};
155              
156             my $key = 'some_key';
157             if (in_scope_container() and my $con = scope_container($key)) {
158             return $con;
159             } else {
160             my $con = MongoDB::Connection->new(@{$class->CONNECT_INFO});
161             scope_container($key, $con) if in_scope_container();
162             return $con;
163             }
164             }
165              
166              
167             =cut
168              
169             sub get_connection {
170 1     1 1 1 my $class = shift;
171 1         2 return MongoDB::Connection->new(@{$class->connect_info});
  1         5  
172             }
173              
174             =head2 connect_info, database_name
175              
176             alias to installed value
177              
178             my $connect_info = $tiny->connect_info;
179              
180             my $database_name = $tiny->database_name;
181              
182             =cut
183              
184             sub connect_info {
185 1     1 1 2 my $class = shift; # or self
186 1         5 util_class_attr('CONNECT_INFO',$class);
187             }
188              
189             sub database_name {
190 0     0 1 0 my $class = shift; # or self
191 0         0 util_class_attr('DATABASE_NAME',$class);
192             }
193              
194             =head2 connection, database
195              
196             alias to connection and database object.
197              
198             my $connection = $tiny->connection; # MongoDB::Connection object
199              
200             my $database = $tiny->database; # MongoDB::Database object
201              
202             =cut
203              
204              
205 0     0 1 0 sub connection { shift->{connection} }
206              
207 0     0 1 0 sub database { shift->{database} }
208              
209             =head2 cursor_class, validator_class, gridfs_class
210              
211             override if you want.
212              
213             MongoDBx::Tiny::Cursor
214            
215             MongoDBx::Tiny::Validator
216            
217             MongoDBx::Tiny::GridFS
218              
219             =cut
220              
221 0     0 1 0 sub cursor_class { 'MongoDBx::Tiny::Cursor' }
222              
223 0     0 1 0 sub validator_class { 'MongoDBx::Tiny::Validator' }
224              
225 0     0 1 0 sub gridfs_class { 'MongoDBx::Tiny::GridFS' }
226              
227             =head2 collection
228              
229             returns MongoDB::Collection
230              
231             $collection = $tiny->collection('collection_name')
232              
233             =cut
234              
235             sub collection {
236 0     0 1 0 my $self = shift;
237 0 0       0 my $name = shift or confess q|no collection name|;
238 0   0     0 my $opt = shift || {no_cache => 0};
239 0 0       0 if ($opt->{no_cache}) {
240 0         0 return $self->database->$name();
241             } else {
242 0         0 my $cache = $self->{collection};
243 0 0       0 return $cache->{$name} if $cache->{$name};
244 0         0 return ($self->{collection}->{$name} = $self->database->$name());
245             }
246             }
247              
248             =head2 connect / disconnect
249              
250             just (re)connect & disconnect
251              
252             =cut
253              
254             sub connect {
255 1     1 1 2 my $self = shift;
256 1         5 $self->disconnect;
257              
258 1         7 my $connection = $self->get_connection;
259 0         0 my $db_name = $self->database_name;
260 0         0 my $database = $connection->$db_name();
261 0         0 $self->{connection} = $connection;
262 0         0 $self->{database} = $database;
263 0         0 $self->{gridfs} = undef;
264              
265 0         0 return $self->{connection};
266             }
267              
268             sub disconnect {
269 1     1 1 2 my $self = shift;
270 1         3 for (qw(connection database gridfs collection)) {
271 4         8 delete $self->{$_};
272             }
273 1         1 return 1;
274             }
275              
276              
277             =head2 insert,create
278              
279             $document_object = $tiny->insert('collection_name' => $document);
280              
281             =cut
282              
283             {
284 3     3   15 no warnings qw(once);
  3         4  
  3         3207  
285             *create = \&insert;
286             }
287              
288             sub insert {
289 0     0 1 0 my $self = shift;
290 0 0       0 my $c_name = shift or confess q/no collection name/;
291 0 0       0 my $document = shift or confess q/no document/;
292              
293 0         0 my $opt = shift;
294 0         0 $opt->{state} = 'insert';
295              
296 0         0 my $validator = $self->validate($c_name,$document,$opt);
297              
298 0 0       0 if ($validator->has_error) {
299 0         0 confess "invalid document: \n" . (Dumper $validator->errors);
300             }
301              
302 0         0 my $d_class = $self->document_class($c_name);
303 0 0       0 unless ($opt->{no_trigger}) {
304 0         0 $d_class->call_trigger('before_insert',$self,$document,$opt) ;
305             }
306              
307 0         0 my $collection = $self->collection($c_name);
308 0         0 my $id = $collection->insert($document);
309 0         0 my $object = $self->single($c_name,$id);
310 0 0       0 unless ($opt->{no_trigger}) {
311 0         0 $d_class->call_trigger('after_insert',$object,$opt);
312             }
313 0         0 return $object;
314             }
315              
316             =head2 single
317              
318             returns MongoDBx::Tiny::Document object.
319              
320             $document_object = $tiny->single('collection_name' => $MongoDB_oid_object);
321            
322             $tiny->single('collection_name' => $oid_text);
323            
324             $query = { field => $val };
325             $tiny->single('collection_name' => $query);
326              
327             =cut
328              
329             sub single {
330             #xxx
331             # 4ff19d717bcc56834b000000
332             # tiny->single("sfa"); # return first value
333 0     0 1 0 my $self = shift;
334 0         0 my $c_name = shift;
335              
336 0         0 my $collection = $self->collection($c_name);
337 0         0 my $document;
338 0         0 my $d_class = $self->document_class($c_name);
339              
340 0         0 my $essential = $d_class->essential; #
341              
342 0         0 my ($proto) = shift;
343 0 0       0 unless (ref $proto eq 'HASH') {
344 0         0 $proto = { _id => "$proto" };
345             }
346 0         0 $proto = util_to_oid($proto,'_id',$d_class->field->list('OID'));
347              
348 0         0 my $reserved= $d_class->query_attributes('single');
349 0 0 0     0 if ($reserved && ( my @attr = keys %$reserved)) {
350 0   0     0 $proto->{$_} ||= $reserved->{$_} for @attr;
351             }
352 0         0 $document = $collection->find_one($proto,$essential);
353              
354             # # needed?
355             # elsif (scalar @_ >= 2) {
356             # my %query = @_;
357             # $document = $collection->find_one(\%query,$essential);
358             # }
359 0 0       0 return unless $document;
360 0         0 $self->document_to_object($c_name,$document);
361             }
362              
363             =head2 search
364              
365             returns MongoDBx::Tiny::Cursor object.
366              
367             $query = { field => $val };
368             $cursor = $tiny->search('collection_name' => $query);
369             while (my $object = $cursor->next) {
370             # warn $object->id;
371             }
372            
373             # list context
374             @object = $tiny->search('collection_name' => $query);
375              
376             =cut
377              
378             sub search {
379 0     0 1 0 my $self = shift;
380             # xxx
381 0         0 my $c_name = shift;
382 0         0 my $collection = $self->collection($c_name);
383 0         0 my $d_class = $self->document_class($c_name);
384 0         0 my $essential = $d_class->essential; #
385 0         0 my $query = shift;
386 0         0 my @operation = @_;
387              
388 0         0 $query = util_to_oid($query,'_id',$d_class->field->list('OID'));
389 0         0 my $reserved= $d_class->query_attributes('search');
390 0 0 0     0 if ($reserved && ( my @attr = keys %$reserved)) {
391 0   0     0 $query->{$_} ||= $reserved->{$_} for @attr;
392             }
393              
394 0         0 my $cursor = $collection->find($query)->fields($essential);
395 0 0       0 if (wantarray) {
396 0         0 return map { $self->document_to_object($c_name,$_) } $cursor->all;
  0         0  
397             } else {
398 0         0 eval "require " . $self->cursor_class;
399 0         0 return $self->cursor_class->new(
400             tiny => $self, c_name => $c_name,cursor => $cursor
401             );
402             }
403             }
404              
405             =head2 update
406              
407             $tiny->update('collection_name',$query,$document);
408              
409             =cut
410              
411             sub update {
412 0     0 1 0 my $self = shift;
413             # xxx
414 0   0     0 my $c_name = shift || confess q/no collection name/;;
415 0   0     0 my $query = shift || confess q/no query/;
416 0         0 my $document = shift;
417 0         0 my $opt = shift;
418 0 0       0 return unless $document;
419 0         0 $opt->{state} = 'update';
420              
421 0         0 my $validator = $self->validate(
422             $c_name,$document,$opt
423             );
424            
425 0 0       0 if ($validator->has_error) {
426 0         0 confess "invalid document: \n" . (Dumper $validator->errors);
427             }
428 0         0 my $d_class = $self->document_class($c_name);
429              
430 0         0 my @object; # xxx
431 0 0 0     0 if (!$opt->{no_trigger} and $d_class->trigger('before_update')) {
432 0         0 my $cursor = $self->search($c_name,$query);
433 0         0 while (my $object = $cursor->next) {
434 0         0 push @object,$object;
435 0         0 $object->call_trigger('before_update',$self,$opt);
436             }
437             }
438              
439 0         0 my $collection = $self->collection($c_name);
440 0         0 $collection->update($query,{ '$set' => $document },{ multiple => 1 });
441              
442 0 0 0     0 if (!$opt->{no_trigger} and $d_class->trigger('after_update')) {
443 0         0 for my $object (@object) {
444 0         0 $object->call_trigger('after_update',$self,$opt);
445             }
446             }
447            
448             # tiny->remove('foo',{ code => 111 });
449             }
450              
451             =head2 remove
452              
453              
454             $tiny->remove('collection_name',$query);
455              
456             =cut
457              
458             sub remove {
459 0     0 1 0 my $self = shift;
460             # xxx
461 0   0     0 my $c_name = shift || confess q/no collection name/;;
462 0   0     0 my $query = shift || confess q/no query/;
463 0   0     0 my $opt = shift || {};
464              
465 0         0 my $d_class = $self->document_class($c_name);
466            
467 0         0 my @object; # xxx
468 0 0 0     0 if (!$opt->{no_trigger} and $d_class->trigger('before_remove')) {
469 0         0 my $cursor = $self->search($c_name,$query);
470 0         0 while (my $object = $cursor->next) {
471 0         0 push @object,$object;
472 0         0 $object->call_trigger('before_remove',$object,$opt);
473             }
474             }
475              
476 0         0 my $collection = $self->collection($c_name);
477 0         0 $collection->remove($query);
478              
479 0 0 0     0 if (!$opt->{no_trigger} and $d_class->trigger('after_remove')) {
480 0         0 for my $object (@object) {
481 0         0 $object->call_trigger('after_remove',$object,$opt);
482             }
483             }
484             }
485              
486             =head2 count
487              
488             $count_num = $tiny->count('collection_name',$query);
489              
490             =cut
491              
492             sub count {
493 0     0 1 0 my $self = shift;
494 0         0 my $c_name = shift;
495 0         0 my $collection = $self->collection($c_name);
496 0         0 my $d_class = $self->document_class($c_name);
497 0         0 return $collection->count(shift);
498             }
499              
500             =head2 document_to_object
501              
502             $document_object = $tiny->document_to_object('collection_name',$document);
503              
504             =cut
505              
506             sub document_to_object {
507 0     0 1 0 my $self = shift;
508 0 0       0 my $c_name = shift or confess q/no collecion name/;
509 0 0       0 my $document = shift or confess q/no document/;
510 0 0       0 confess q/no id/ unless $document->{_id};
511              
512 0         0 my $d_class = $self->document_class($c_name);
513              
514 0         0 return $d_class->new($document,$self);
515             }
516              
517             =head2 validate
518              
519             $validator = $tiny->validate('collecion_name',$document,$opt);
520              
521             my $validator = $tiny->validate(
522             'foo',
523             { code => 123, name => "foo_123"},
524             { state => 'insert' }
525             );
526             my $foo1 = $tiny->insert(
527             'foo',
528             $validator->document,
529             { state => 'insert', no_validate => 1 }
530             );
531             # erros: [{ field => 'field1', code => 'errorcode', message => 'message1' },,,]
532             my @erros = $validator->erros;
533            
534             my @fields = $validator->errors('field');
535             my @error_code = $validator->errors('code');
536             my @error_message = $validator->errors('message');
537              
538             =cut
539              
540             sub validate {
541 0     0 1 0 my $self = shift;
542 0 0       0 my $c_name = shift or confess q/no collecion_name/;
543 0 0       0 my $document = shift or confess q/no document/;
544 0         0 my $opt = shift;
545              
546 0         0 Params::Validate::validate_with(
547             params => $opt,
548             spec => {
549             state => 1,# insert,update
550             no_trigger => 0,
551             no_validate => 0,
552             },
553             allow_extra => 1,
554             );
555            
556 0         0 $opt->{tiny} = $self;
557 0         0 eval "require " . $self->validator_class;
558 0         0 my $validator = $self->validator_class->new(
559             $c_name,
560             $document,
561             $self,
562             );
563            
564 0         0 return $validator->check($opt);
565             }
566              
567             =head2 gridfs
568              
569             returns MongoDBx::Tiny::GridFS
570              
571             $gridfs = $tiny->gridfs();
572            
573             $gridfs = $tiny->gridfs({database => $mongo_databse_object });
574            
575             $gridfs = $tiny->gridfs({fields => 'other_filename' });
576              
577             my $gridfs = $tiny->gridfs;
578             $gridfs->put('/tmp/foo.txt', {"filename" => 'foo.txt' });
579             my $foo_txt = $gridfs->get({ filename => 'foo.txt' })->slurp;
580            
581             $gridfs->put('/tmp/bar.txt','bar.txt');
582             my $bar_txt = $gridfs->get('bar.txt')->slurp;
583              
584             =cut
585              
586             sub gridfs {
587 0     0 1 0 my $self = shift;
588 0   0     0 my $opt = shift || {};
589 0         0 my %opt = Params::Validate::validate_with(
590             params => $opt,
591             spec => {
592             database => {optional => 1},
593             fields => {optional => 1, default => 'filename'},
594             }
595             );
596              
597 0   0     0 my $database = $opt{database} || $self->database;
598              
599 0         0 eval "require " . $self->gridfs_class;
600 0 0       0 return $self->{gridfs} if $self->{gridfs};
601             $self->{gridfs} = $self->gridfs_class->new(
602             $database->get_gridfs,$opt{fields}
603 0         0 );
604             }
605              
606              
607             =head2 document_class
608              
609             $document_class_name = $tiny->document_class('collecion_name');
610              
611             =cut
612              
613             sub document_class {
614 0     0 1 0 my $self = shift;
615 0 0       0 my $c_name = shift or confess q/no collection name/;
616 0         0 return util_document_class($c_name,ref $self);
617             }
618              
619             =head2 load_plugin
620              
621             # --------------------
622            
623             package MyDB;
624             use MongoDBx::Tiny;
625            
626             LOAD_PLUGIN('One');
627             LOAD_PLUGIN('Two');
628            
629             # --------------------
630             package MongoDBx::Tiny::Plugin::PluginName;
631             use strict;
632             use warnings;
633             use utf8;
634            
635             our @EXPORT = qw/function_for_plugin/;
636            
637             sub function_for_plugin {}
638            
639             # --------------------
640            
641             $tiny->function_for_plugin;
642              
643             =cut
644              
645             sub load_plugin {
646 1     1 1 2 my ($proto, $pkg) = @_;
647            
648 1 50       3 my $class = ref $proto ? ref $proto : $proto;
649 1 50       3 $pkg = $pkg =~ s/^\+// ? $pkg : "MongoDBx::Tiny::Plugin::$pkg";
650 1         50 eval "require $pkg ";
651              
652 3     3   14 no strict 'refs';
  3         4  
  3         993  
653 1         2 for my $method ( @{"${pkg}::EXPORT"} ) {
  1         4  
654 2 50       18 next if $class->can($method);
655 2         9 *{$class . '::' . $method} = $pkg->can($method);
  2         6  
656             }
657             }
658              
659             =head2 process
660              
661             [EXPERIMENTAL]
662              
663             $tiny->process('collecion_name','some',@args);
664              
665             $tiny->process('foo','some',$validator,$arg); # just call Data::Foo::process_some
666            
667             #
668             sub process_foo {
669             my ($class,$tiny,$validator,$arg) = @_;
670             }
671              
672             =cut
673              
674             sub process {
675             # xxx
676 0     0 1   my $self = shift;
677 0 0         my $c_name = shift or confess q/no collecion name/;
678 0 0         my $method = shift or confess q/no process method name/;
679 0           my $d_class = $self->document_class($c_name);
680 0           my $process_method = sprintf "process_%s", $method;
681 0           $d_class->$process_method($self,@_);
682             }
683              
684             =head2 set_indexes
685              
686             [EXPERIMENTAL]
687              
688             $tiny->set_indexes('collection_name');
689              
690             =cut
691              
692             sub set_indexes {
693 0     0 1   my $self = shift;
694 0           my $c_name = shift;
695 0           my $collection = $self->collection($c_name);
696 0           my $d_class = $self->document_class($c_name);
697 0   0       my $indexes = $d_class->indexes || [];
698              
699              
700 0           my $ns = sprintf "%s.%s", $self->database_name,$c_name;
701              
702 0           for my $index (@$indexes) {
703              
704 0           my ($field,$index_opt,$opt) = @$index;
705 0           require Tie::IxHash;
706 0 0         if (ref $field eq 'ARRAY') {
707 0           $field = Tie::IxHash->new(@$field);
708             }
709 0 0         my $index_target = ref $field ? $field : { $field => 1 };
710 0           my ($index_exists) = $self->collection('system.indexes')->find({ns => $ns,key => $index_target})->all;
711            
712 0 0         if (!$index_exists) {
713 0           $collection->ensure_index($index_target,$index_opt);
714             }
715             }
716             }
717              
718             =head2 unset_indexes
719              
720             [EXPERIMENTAL]
721              
722             # drop indexes without "_id";
723             $tiny->unset_indexes('collection_name');
724              
725             =cut
726              
727             sub unset_indexes {
728 0     0 1   my $self = shift;
729 0           my $c_name = shift;
730 0           my $collection = $self->collection($c_name);
731              
732             #> db.system.indexes.find({ns:"my_data.foo",key:{$ne : {"_id":1}}});
733 0           my $ns = sprintf "%s.%s", $self->database_name,$c_name;
734 0           my @index = $self->collection('system.indexes')->find({ns => $ns,key => { '$ne' => {"_id" => 1}}})->all;
735 0           for (@index) {
736 0           $collection->drop_index($_->{key});
737             }
738             }
739              
740       0     sub DESTROY {
741             # xxx
742             }
743              
744              
745             1;
746              
747             __END__
748              
749             =head1 SEE ALSO
750              
751             =over
752              
753             =item MongoDBx::Tiny::Document
754              
755             =item MongoDBx::Tiny::Attributes
756              
757             =item MongoDBx::Tiny::Relation
758              
759             =item MongoDBx::Tiny::Util
760              
761             =item MongoDBx::Tiny::Cursor
762              
763             =item MongoDBx::Tiny::Validator
764              
765             =item MongoDBx::Tiny::GridFS
766              
767             =item MongoDBx::Tiny::GridFS::File
768              
769             =item MongoDBx::Tiny::Plugin::SingleByCache
770              
771             =back
772              
773             =head1 SUPPORT
774              
775             L<https://github.com/naoto43/mongodbx-tiny/>
776              
777             =head1 AUTHOR
778              
779             Naoto ISHIKAWA, C<< <toona at seesaa.co.jp> >>
780              
781             =head1 LICENSE AND COPYRIGHT
782              
783             Copyright 2013 Naoto ISHIKAWA.
784              
785             This program is free software; you can redistribute it and/or modify it
786             under the terms of either: the GNU General Public License as published
787             by the Free Software Foundation; or the Artistic License.
788              
789             See http://dev.perl.org/licenses/ for more information.
790              
791              
792             =cut