File Coverage

lib/Coteng.pm
Criterion Covered Total %
statement 171 180 95.0
branch 40 62 64.5
condition 37 66 56.0
subroutine 33 33 100.0
pod 16 18 88.8
total 297 359 82.7


line stmt bran cond sub pod time code
1             package Coteng;
2 1     1   1326 use 5.008005;
  1         3  
3 1     1   4 use strict;
  1         1  
  1         16  
4 1     1   9 use warnings;
  1         1  
  1         39  
5              
6             our $VERSION = "0.11";
7             our $DBI_CLASS = 'Coteng::DBI';
8              
9 1     1   8 use Carp ();
  1         1  
  1         11  
10 1     1   407 use Module::Load ();
  1         762  
  1         15  
11 1     1   339 use Scope::Container;
  1         406  
  1         50  
12 1     1   362 use Scope::Container::DBI;
  1         13798  
  1         24  
13 1     1   6 use SQL::NamedPlaceholder ();
  1         1  
  1         30  
14             use Class::Accessor::Lite::Lazy (
15 1         8 rw => [qw(
16             dbh
17             current_dbname
18             connect_info
19             )],
20             rw_lazy => [qw(
21             sql_builder
22             )],
23             new => 1
24 1     1   401 );
  1         1825  
25              
26 1     1   372 use Coteng::DBI;
  1         2  
  1         36  
27 1     1   253 use Coteng::QueryBuilder;
  1         2  
  1         1316  
28              
29              
30             sub db {
31 2     2 1 4497 my ($self, $dbname) = @_;
32 2 50       5 $dbname or Carp::croak "dbname required";
33 2         4 $self->current_dbname($dbname);
34 2         9 $self->dbh($dbname);
35 2         6 $self;
36             }
37              
38             sub dbh {
39 7     7   5116 my ($self, $dbname) = @_;
40 7 50 66     22 $dbname ||= $self->current_dbname or Carp::croak 'dbname or current_dbname required';
41              
42 7   33     21 my $db_info = $self->{connect_info}->{$dbname} || Carp::croak "'$dbname' doesn't exist";
43              
44 7         27 my ($dsn, $user, $passwd, $attr) = ('', '', '', {});
45 7 100       16 if (ref($db_info) eq 'HASH') {
    50          
46 5   33     9 $dsn = $db_info->{dsn} || Carp::croak "dsn required";
47 5 50       8 $user = defined $db_info->{user} ? $db_info->{user} : '';
48 5 50       6 $passwd = defined $db_info->{passwd} ? $db_info->{passwd} : '';
49 5         5 $attr = $db_info->{attr};
50             }
51             elsif (ref($db_info) eq 'ARRAY') {
52 2         3 ($dsn, $user, $passwd, $attr) = @$db_info;
53             }
54             else {
55 0         0 Carp::croak 'connect_info->{$dbname} must be HASHref, or ARRAYref';
56             }
57              
58 7         12 load_if_class_not_loaded($DBI_CLASS);
59              
60 7 100       15 unless (in_scope_container) {
61             # define $CONTEXT forcelly to enable Scope::Container::DBI cache
62 4         14 $self->{_dbh_container_dummy} = start_scope_container();
63             }
64              
65 7   33     53 $attr->{RootClass} ||= $DBI_CLASS;
66 7         20 my $dbh = Scope::Container::DBI->connect($dsn, $user, $passwd, $attr);
67 7         579 $dbh;
68             }
69              
70             sub _build_sql_builder {
71 1     1   9 my ($self) = @_;
72 1         3 return Coteng::QueryBuilder->new(driver => $self->dbh->{Driver}{Name});
73             }
74              
75             sub single_by_sql {
76 21     21 1 4459 my ($self, $sql, $binds, $class) = @_;
77              
78 21   100     38 my $row = $self->dbh->select_row($sql, @$binds) || '';
79 21 100 100     1976 if ($class && $row) {
80 3         8 load_if_class_not_loaded($class);
81 3         530 $row = $class->new($row);
82             }
83 21         56 return $row;
84             }
85              
86             sub single_named {
87 2     2 1 2519 my ($self, $sql, $bind_values, $class) = @_;
88 2         6 ($sql, my $binds) = SQL::NamedPlaceholder::bind_named($sql, $bind_values);
89 2         43 my $row = $self->single_by_sql($sql, $binds, $class);
90 2         4 return $row;
91             }
92              
93             sub search_by_sql {
94 8     8 1 4834 my ($self, $sql, $binds, $class) = @_;
95 8   50     16 my $rows = $self->dbh->select_all($sql, @$binds) || [];
96 8 100 100     1052 if ($class && @$rows) {
97 3         7 load_if_class_not_loaded($class);
98 3         5 $rows = [ map { $class->new($_) } @$rows ];
  3         9  
99             }
100 8         38 return $rows;
101             }
102              
103             sub search_named {
104 2     2 1 2536 my ($self, $sql, $bind_values, $class) = @_;
105 2         6 ($sql, my $binds) = SQL::NamedPlaceholder::bind_named($sql, $bind_values);
106 2         38 my $rows = $self->search_by_sql($sql, $binds, $class);
107 2         4 return $rows;
108             }
109              
110             sub execute {
111 18     18 1 2174 my $self = shift;
112 18         28 $self->dbh->query($self->_expand_args(@_));
113             }
114              
115             sub _validate_where {
116 19     19   17 my ($self, $where) = @_;
117 19 50 66     53 if (ref($where) ne "HASH" && ref($where) ne "ARRAY"
      66        
      33        
118             && ref($where) ne "SQL::Maker::Condition" && ref($where) ne "SQL::QueryMaker") {
119 0         0 Carp::croak "'where' required to be HASH or ARRAY or SQL::Maker::Condition or SQL::QueryMaker";
120             }
121             }
122              
123             sub single {
124 16     16 1 5948 my ($self, $table, $where, $opt) = @_;
125 16         14 my $class = do {
126 16         16 my $klass = pop;
127 16 100       33 ref($klass) ? undef : $klass;
128             };
129 16 50       28 if (ref($opt) ne "HASH") {
130 16         13 $opt = {};
131             }
132              
133 16         28 $self->_validate_where($where);
134              
135 16         20 $opt->{limit} = 1;
136              
137             my ($sql, @binds) = $self->sql_builder->select(
138             $table,
139 16   50     36 $opt->{columns} || ['*'],
140             $where,
141             $opt
142             );
143 16         3220 my $row = $self->single_by_sql($sql, \@binds, $class);
144 16         36 return $row;
145             }
146              
147             sub search {
148 3     3 1 4707 my ($self, $table, $where, $opt) = @_;
149 3         2 my $class = do {
150 3         4 my $klass = pop;
151 3 100       6 ref($klass) ? undef : $klass;
152             };
153 3 50       8 if (ref($opt) ne "HASH") {
154 3         4 $opt = {};
155             }
156              
157 3         4 $self->_validate_where($where);
158              
159             my ($sql, @binds) = $self->sql_builder->select(
160             $table,
161 3   50     7 $opt->{'columns'} || ['*'],
162             $where,
163             $opt
164             );
165 3         522 my $rows = $self->search_by_sql($sql, \@binds, $class);
166 3         6 return $rows;
167             }
168              
169             sub fast_insert {
170 14     14 1 20064 my ($self, $table, $args, $prefix) = @_;
171              
172 14         39 my ($sql, @binds) = $self->sql_builder->insert(
173             $table,
174             $args,
175             { prefix => $prefix },
176             );
177 14         823 $self->execute($sql, @binds);
178 14         980 return $self->dbh->last_insert_id($table);
179             }
180              
181             sub insert {
182 3     3 1 1629 my $self = shift;
183 3         5 my ($table, $args, $opt) = @_;
184 3         3 my $class = do {
185 3         1 my $klass = pop;
186 3 50       8 ref($klass) ? undef : $klass;
187             };
188 3 50       8 if (ref($opt) ne "HASH") {
189 3         4 $opt = {};
190             }
191              
192 3 50 33     8 if (ref($args) ne "HASH" && ref($args) ne "ARRAY") {
193 0         0 Carp::croak "'args' required to be HASH or ARRAY";
194             }
195              
196 3   50     11 $opt->{primary_key} ||= "id";
197              
198 3         8 my $id = $self->fast_insert($table, $args, $opt->{prefix});
199 3         54 return $self->single($table, { $opt->{primary_key} => $id }, $class);
200             }
201              
202             sub bulk_insert {
203 1     1 1 1658 my ($self, $table, $args) = @_;
204              
205 1 50       1 return undef unless scalar(@{$args || []});
  1 50       4  
206              
207 1         3 my $dbh = $self->dbh;
208 1 50       14 my $can_multi_insert = $dbh->{Driver}{Name} eq 'mysql' ? 1 : 0;
209              
210 1 50       3 if ($can_multi_insert) {
211 0         0 my ($sql, @binds) = $self->sql_builder->insert_multi($table, $args);
212 0         0 $self->execute($sql, @binds);
213             } else {
214             # use transaction for better performance and atomicity.
215 1         7 my $txn = $dbh->txn_scope();
216 1         85 for my $arg (@$args) {
217 2         4 $self->insert($table, $arg);
218             }
219 1         6 $txn->commit;
220             }
221             }
222              
223             sub update {
224 1     1 1 21 my ($self, $table, $args, $where) = @_;
225              
226 1         4 my ($sql, @binds) = $self->sql_builder->update($table, $args, $where);
227 1         126 $self->execute($sql, @binds);
228             }
229              
230             sub delete {
231 2     2 1 42 my ($self, $table, $where) = @_;
232              
233 2         5 my ($sql, @binds) = $self->sql_builder->delete($table, $where);
234 2         177 $self->execute($sql, @binds);
235             }
236              
237             sub count {
238 1     1 1 21 my ($self, $table, $column, $where, $opt) = @_;
239              
240 1 50       3 if (ref $column eq 'HASH') {
241 0         0 Carp::croak('Do not pass HashRef to second argument. Usage: $db->count($table[, $column[, $where[, $opt]]])');
242             }
243              
244 1   50     2 $column ||= '*';
245              
246 1         4 my ($sql, @binds) = $self->sql_builder->select($table, [\"COUNT($column)"], $where, $opt);
247              
248 1         292 my ($cnt) = $self->dbh->select_one($sql, @binds);
249 1         91 $cnt;
250             }
251              
252             sub last_insert_id {
253 1     1 1 5 my $self = shift;
254 1         3 $self->dbh->last_insert_id;
255             }
256              
257             sub txn_scope {
258 1     1 1 1429 my $self = shift;
259 1         3 $self->dbh->txn_scope;
260             }
261              
262             sub _expand_args (@) {
263 18     18   43 my ($class, $query, @args) = @_;
264              
265 18 100 100     66 if (@args == 1 && ref $args[0] eq 'HASH') {
266 1         4 ( $query, my $binds ) = SQL::NamedPlaceholder::bind_named($query, $args[0]);
267 1         21 @args = @$binds;
268             }
269              
270 18         48 return ($query, @args);
271             }
272              
273             sub load_if_class_not_loaded {
274 13     13 0 12 my $class = shift;
275 13 100       15 if (! is_class_loaded($class)) {
276 1         5 Module::Load::load $class;
277             }
278             }
279              
280             # stolen from Mouse::PurePerl
281             sub is_class_loaded {
282 13     13 0 12 my $class = shift;
283              
284 13 50 33     67 return 0 if ref($class) || !defined($class) || !length($class);
      33        
285              
286 13         14 my $pack = \%::;
287              
288 13         34 foreach my $part (split('::', $class)) {
289 31         22 $part .= '::';
290 31 100       48 return 0 if !exists $pack->{$part};
291              
292 30         24 my $entry = \$pack->{$part};
293 30 50       42 return 0 if ref($entry) ne 'GLOB';
294 30         14 $pack = *{$entry}{HASH};
  30         41  
295             }
296              
297 12 50       10 return 0 if !%{$pack};
  12         37  
298              
299             # check for $VERSION or @ISA
300             return 1 if exists $pack->{VERSION}
301 12 0 33     30 && defined *{$pack->{VERSION}}{SCALAR} && defined ${ $pack->{VERSION} };
  0   33     0  
  0         0  
302             return 1 if exists $pack->{ISA}
303 12 50 66     21 && defined *{$pack->{ISA}}{ARRAY} && @{ $pack->{ISA} } != 0;
  7   66     25  
  7         33  
304              
305             # check for any method
306 5         5 foreach my $name( keys %{$pack} ) {
  5         11  
307 10         8 my $entry = \$pack->{$name};
308 10 100 66     17 return 1 if ref($entry) ne 'GLOB' || defined *{$entry}{CODE};
  10         36  
309             }
310              
311             # fail
312 0           return 0;
313             }
314              
315             1;
316             __END__
317              
318             =encoding utf-8
319              
320             =head1 NAME
321              
322             Coteng - Lightweight Teng
323              
324             =head1 SYNOPSIS
325              
326             use Coteng;
327              
328             my $coteng = Coteng->new({
329             connect_info => {
330             db_master => {
331             dsn => 'dbi:mysql:dbname=server;host=dbmasterhost',
332             user => 'nobody',
333             passwd => 'nobody',
334             },
335             db_slave => {
336             dsn => 'dbi:mysql:dbname=server;host=dbslavehost',
337             user => 'nobody',
338             passwd => 'nobody',
339             },
340             },
341             });
342              
343             # or
344              
345             my $coteng = Coteng->new({
346             connect_info => {
347             db_master => [
348             'dbi:mysql:dbname=server;host=dbmasterhost', 'nobody', 'nobody', {
349             PrintError => 0,
350             }
351             ],
352             db_slave => [
353             'dbi:mysql:dbname=server;host=dbslavehost', 'nobody', 'nobody',
354             ],
355             },
356             });
357              
358             # or
359              
360             use Coteng::DBI;
361              
362             my $dbh1 = Coteng::DBI->connect('dbi:mysql:dbname=server;host=dbmasterhost', 'nobody', 'npbody');
363             my $dbh2 = Coteng::DBI->connect('dbi:mysql:dbname=server;host=dbslavehost', 'nobody', 'npbody');
364              
365             my $coteng = Coteng->new({
366             dbh => {
367             db_master => $dbh1,
368             db_slave => $dbh2,
369             },
370             });
371              
372              
373             my $inserted_host = $coteng->db('db_master')->insert(host => {
374             name => 'host001',
375             ipv4 => '10.0.0.1',
376             status => 'standby',
377             }, "Your::Model::Host");
378             my $last_insert_id = $coteng->db('db_master')->fast_insert(host => {
379             name => 'host001',
380             ipv4 => '10.0.0.1',
381             status => 'standby',
382             });
383             my $host = $coteng->db('db_slave')->single(host => {
384             name => 'host001',
385             }, "Your::Model::Host");
386             my $hosts = $coteng->db('db_slave')->search(host => {
387             name => 'host001',
388             }, "Your::Model::Host");
389              
390             my $updated_row_count = $coteng->db('db_master')->update(host => {
391             status => "working",
392             }, {
393             id => 10,
394             });
395             my $deleted_row_count = $coteng->db('db_master')->delete(host => {
396             id => 10,
397             });
398              
399             ## no blessed return value
400              
401             my $hosts = $coteng->db('db_slave')->single(host => {
402             name => 'host001',
403             });
404              
405             # Raw SQL interface
406              
407             my $host = $coteng->db('db_slave')->single_named(q[
408             SELECT * FROM host where name = :name LIMIT 1
409             ], { name => "host001" }, "Your::Model::Host");
410             my $host = $coteng->db('db_slave')->single_by_sql(q[
411             SELECT * FROM host where name = ? LIMIT 1
412             ], [ "host001" ], "Your::Model::Host");
413              
414             my $hosts = $coteng->db('db_slave')->search_named(q[
415             SELECT * FROM host where status = :status
416             ], { status => "working" }, "Your::Model::Host");
417             my $hosts = $coteng->db('db_slave')->search_named(q[
418             SELECT * FROM host where status = ?
419             ], [ "working" ], "Your::Model::Host");
420              
421              
422             package Your::Model::Host;
423              
424             use Class::Accessor::Lite(
425             rw => [qw(
426             id
427             name
428             ipv4
429             status
430             )],
431             new => 1,
432             );
433              
434              
435             =head1 DESCRIPTION
436              
437             Coteng is a lightweight L<Teng>, just as very simple DBI wrapper.
438             Teng is a simple and good designed ORMapper, but it has a little complicated functions such as the row class, iterator class, the schema definition class (L<Teng::Row>, L<Teng::Iterator> and L<Teng::Schema>).
439             Coteng doesn't have such functions and only has very similar Teng SQL interface.
440              
441             Coteng itself has no transaction and last_insert_id implementation, but has thir interface thanks to L<DBIx::Sunny>.
442             (Coteng uses DBIx::Sunny as a base DB handler.)
443              
444             =head1 METHODS
445              
446             Coteng provides a number of methods to all your classes,
447              
448             =over
449              
450             =item $coteng = Coteng->new(\%args)
451              
452             Creates a new Coteng instance.
453              
454             # connect new database connection.
455             my $coteng = Coteng->new({
456             connect_info => {
457             dbname => {
458             dsn => $dsn,
459             user => $user,
460             passwd => $passwd,
461             attr => \%attr,
462             },
463             },
464             });
465              
466             Arguments can be:
467              
468             =over
469              
470             =item * C<connect_info>
471              
472             Specifies the information required to connect to the database.
473             The argument should be a reference to a nested hash in the form:
474              
475             {
476             dbname => {
477             dsn => $dsn,
478             user => $user,
479             passwd => $passwd,
480             attr => \%attr,
481             },
482             },
483              
484             or a array referece in the form
485              
486             {
487             dbname => [ $dsn, $user, $passwd, \%attr ],
488             },
489              
490             'dbname' is something you like to identify a database type such as 'db_master', 'db_slave', 'db_batch'.
491              
492             =item C<$row = $coteng-E<gt>db($dbname)>
493              
494             Set internal current db by $dbname registered in 'new' method.
495             Returns Coteng object ($self) to enable you to use method chain like below.
496              
497             my $row = $coteng->db('db_master')->insert();
498              
499             =item C<$row = $coteng-E<gt>insert($table, \%row_data, [\%opt], [$class])>
500              
501             Inserts a new record. Returns the inserted row object blessed $class.
502             If it's not specified $class, returns the hash reference.
503              
504             my $row = $coteng->db('db_master')->insert(host => {
505             id => 1,
506             ipv4 => '192.168.0.0',
507             }, { primary_key => 'host_id', prefix => 'SELECT DISTINCT' } );
508              
509             'primary_key' default value is 'id'.
510             'prefix' default value is 'SELECT'.
511              
512             If a primary key is available, it will be fetched after the insert -- so
513             an INSERT followed by SELECT is performed. If you do not want this, use
514             C<fast_insert>.
515              
516             =item C<$last_insert_id = $teng-E<gt>fast_insert($table_name, \%row_data, [$prefix]);>
517              
518             insert new record and get last_insert_id.
519              
520             no creation row object.
521              
522             =item C<$teng-E<gt>bulk_insert($table_name, \@rows_data)>
523              
524             Accepts either an arrayref of hashrefs.
525             Each hashref should be a structure suitable for your table schema.
526             The second argument is an arrayref of hashrefs. All of the keys in these hashrefs must be exactly the same.
527              
528             insert many record by bulk.
529              
530             example:
531              
532             $coteng->db('db_master')->bulk_insert(host => [
533             {
534             id => 1,
535             name => 'host001',
536             },
537             {
538             id => 2,
539             name => 'host002',
540             },
541             {
542             id => 3,
543             name => 'host003',
544             },
545             ]);
546              
547             =item C<$update_row_count = $coteng-E<gt>update($table_name, \%update_row_data, [\%update_condition])>
548              
549             Calls UPDATE on C<$table_name>, with values specified in C<%update_ro_data>, and returns the number of rows updated. You may optionally specify C<%update_condition> to create a conditional update query.
550              
551             my $update_row_count = $coteng->db('db_master')->update(host =>
552             {
553             name => 'host001',
554             },
555             {
556             id => 1
557             }
558             );
559             # Executes UPDATE user SET name = 'host001' WHERE id = 1
560              
561             =item C<$delete_row_count = $coteng-E<gt>delete($table, \%delete_condition)>
562              
563             Deletes the specified record(s) from C<$table> and returns the number of rows deleted. You may optionally specify C<%delete_condition> to create a conditional delete query.
564              
565             my $rows_deleted = $coteng->db('db_master')->delete(host => {
566             id => 1
567             });
568             # Executes DELETE FROM host WHERE id = 1
569              
570             =item C<$row = $teng-E<gt>single($table_name, \%search_condition, \%search_attr, [$class])>
571              
572             Returns (hash references or $class objects) or empty string ('') if sql result is empty
573              
574             my $row = $coteng->db('db_slave')->single(host => { id => 1 }, 'Your::Model::Host');
575              
576             my $row = $coteng->db('db_slave')->single(host => { id => 1 }, { columns => [qw(id name)] });
577              
578             =item C<$rows = $coteng-E<gt>search($table_name, [\%search_condition, [\%search_attr]], [$class])>
579              
580             Returns array reference of (hash references or $class objects) or empty array reference ([]) if sql result is empty.
581              
582             my $rows = $coteng->db('db_slave')->search(host => {id => 1}, {order_by => 'id'}, 'Your::Model::Host');
583              
584             =item C<$row = $teng-E<gt>single_named($sql, [\%bind_values], [$class])>
585              
586             Gets one record from execute named query
587             Returns empty string ( '' ) if sql result is empty.
588              
589             my $row = $coteng->db('db_slave')->single_named(q{SELECT id,name FROM host WHERE id = :id LIMIT 1}, {id => 1}, 'Your::Model::Host');
590              
591             =item C<$row = $coteng-E<gt>single_by_sql($sql, [\@bind_values], $class)>
592              
593             Gets one record from your SQL.
594             Returns empty string ('') if sql result is empty.
595              
596             my $row = $coteng->db('db_slave')->single_by_sql(q{SELECT id,name FROM user WHERE id = ? LIMIT 1}, [1], 'user');
597              
598             =item C<$rows = $coteng-E<gt>search_named($sql, [\%bind_values], [$class])>
599              
600             Execute named query
601             Returns empty array reference ([]) if sql result is empty.
602              
603             my $itr = $coteng->db('db_slave')->search_named(q[SELECT * FROM user WHERE id = :id], {id => 1}, 'Your::Model::Host');
604              
605             If you give array reference to value, that is expanded to "(?,?,?,?)" in SQL.
606             It's useful in case use IN statement.
607              
608             # SELECT * FROM user WHERE id IN (?,?,?);
609             # bind [1,2,3]
610             my $rows = $coteng->db('db_slave')->search_named(q[SELECT * FROM user WHERE id IN :ids], {ids => [1, 2, 3]}, 'Your::Model::Host');
611              
612             =item C<$rows = $coteng-E<gt>search_by_sql($sql, [\@bind_values], [$class])>
613              
614             Execute your SQL.
615             Returns empty array reference ([]) if sql result is empty.
616              
617             my $rows = $coteng->db('db_slave')->search_by_sql(q{
618             SELECT
619             id, name
620             FROM
621             host
622             WHERE
623             id = ?
624             }, [ 1 ]);
625              
626             =item C<$count = $coteng-E<gt>count($table, [$table[, $column[, $where[, $opt]]])>
627              
628             Execute count SQL.
629             Returns record counts.
630              
631             my $count = $coteng->count(host, '*', {
632             status => 'working',
633             });
634              
635             =item C<$sth = $coteng-E<gt>execute($sql, [\@bind_values|@bind_values])>
636              
637             execute query and get statement handler.
638              
639             =item C<$id = $coteng-E<gt>last_insert_id()>
640              
641             Returns last_insert_id.
642              
643             =item C<$txn = $coteng-E<gt>txn_scope()>
644              
645             Returns DBIx::TransactionManager::ScopeGuard object
646              
647             {
648             my $txn = $coteng->db('db_master')->txn_scope();
649             ...
650             $txn->commit;
651             }
652              
653             =back
654              
655             =head1 NOTE
656              
657             =over
658              
659             =item USING DBI CLASSES
660              
661             default DBI CLASS is 'Coteng::DBI' (Coteng::DBI's parent is DBIx::Sunny). You can change DBI CLASS via $Coteng::DBI_CLASS.
662             'Your::DBI' class should be followed by DBIx::Sunny interface.
663              
664             local $Coteng::DBI_CLASS = 'Your::DBI';
665             my $coteng = Coteng->new({ connect_info => ... });
666             $coteng->dbh('db_master')->insert(...);
667              
668             =back
669              
670             =head1 SEE ALSO
671              
672             =over
673              
674             =item L<Teng>
675              
676             =item L<DBIx::Sunny>
677              
678             =item L<SQL::Maker>
679              
680             =back
681              
682             =head1 LICENSE
683              
684             Copyright (C) y_uuki.
685              
686             This library is free software; you can redistribute it and/or modify
687             it under the same terms as Perl itself.
688              
689             =head1 AUTHOR
690              
691             y_uuki E<lt>yuki.tsubo@gmail.comE<gt>
692              
693             =cut
694