File Coverage

blib/lib/Data/Type.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1            
2             # (c) 2004 by Murat Uenalan. All rights reserved. Note: This program is
3             # free software; you can redistribute it and/or modify it under the same
4             # terms as perl itself
5             # $Revision: 1.39 $
6             # $Header: /cygdrive/y/cvs/perl/modules/Data/Type/Type.pm.tmpl,v 1.39 2003/04/12 12:48:38 Murat Exp $
7            
8             package Data::Type;
9            
10             BEGIN
11             {
12 20     20   340010 use Regexp::Box;
  0            
  0            
13            
14             our $rebox = Regexp::Box->new( name => 'Data::Type custom datatypes' );
15             }
16            
17             our $VERSION = "0.02.02";
18            
19             our $DEBUG = 0;
20            
21             require 5.005_62; use strict; use warnings;
22            
23             use Carp;
24            
25             use Class::Maker;
26            
27             use Class::Maker::Exception qw(:try);
28            
29             use Locale::Language; # required by langcode langname
30            
31             use IO::Extended qw(:all);
32            
33             use Data::Iter qw(:all);
34            
35             use Exporter;
36            
37             our @ISA = qw( Exporter );
38            
39             use subs qw(try with);
40            
41             our %EXPORT_TAGS =
42             (
43             'all' => [qw(is isnt valid dvalid catalog summary try with)],
44            
45             'valid' => [qw(is isnt valid dvalid)],
46            
47             # same as :valid
48            
49             'is' => [qw(is isnt valid dvalid)],
50            
51             'try' => [qw(try with)],
52             );
53            
54             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
55            
56             our @EXPORT = ();
57            
58             # modules loaded registry (used by _load_dependency method)
59            
60             our $_loaded = {};
61            
62             our @_requested;
63            
64             our $_master_prefix = ''; # prefix all datatypes with that string
65            
66             our $_callers_pkg = '';
67            
68             our $_used_import_opts = {};
69            
70             use Data::Type::Collection;
71            
72             sub import
73             {
74             # Fetch the callers pkg
75            
76             $_callers_pkg = scalar caller;
77            
78             warnfln "caller is %S", $_callers_pkg if $Data::Type::DEBUG;
79            
80             # Reset master_prefix each time this module is used
81             # So there is no confusion if its double used and the old
82             # master_prefix is active
83            
84             $Data::Type::_master_prefix = '';
85            
86             @_requested = ();
87            
88             push @_requested, 'Std';
89            
90             my @copy;
91            
92             foreach my $id (@_)
93             {
94             if( $id =~ /^\+/ )
95             {
96             # $id is an alias to @_ entry and therefore read-only
97            
98             warnfln "Import Collection statement for: %S", $id if $Data::Type::DEBUG;
99            
100             my $cp = $id;
101            
102             $cp =~ s/^\+//;
103            
104             if( $cp eq 'ALL' )
105             {
106             warnfln "Collections %S for import registered", join( ', ', keys %$Data::Type::Collection::_ids ) if $Data::Type::DEBUG;
107            
108             push @_requested, keys %$Data::Type::Collection::_ids;
109             }
110             else
111             {
112             push @_requested, $cp;
113             }
114             }
115             elsif( $id =~ /^<(.+)>$/ )
116             {
117             $_used_import_opts->{MASTER_PREFIX} = $Data::Type::_master_prefix = $1;
118             }
119             elsif( $id =~ /^_$/ )
120             {
121             warnfln "Export option: UNDERSCORE activated." if $Data::Type::DEBUG;
122            
123             $Data::Type::_reformat_name = sub { $_[0] =~ s/::/_/g; };
124            
125             $_used_import_opts->{UNDERSCORE} = 1;
126             }
127             elsif( $id =~ /^debug\+\+$/i )
128             {
129             $_used_import_opts->{DEBUG} = $Data::Type::DEBUG++;
130             }
131             elsif( $id =~ /^debug\-\-$/i )
132             {
133             $_used_import_opts->{DEBUG} = $Data::Type::DEBUG--;
134             }
135             else
136             {
137             push @copy, $id;
138             }
139             }
140            
141             if( defined $_used_import_opts->{UNDERSCORE} )
142             {
143             unless( defined $_used_import_opts->{MASTER_PREFIX} )
144             {
145             $_used_import_opts->{MASTER_PREFIX} = $Data::Type::_master_prefix = $_callers_pkg.'::';
146             }
147             }
148            
149             my %requested;
150            
151             $requested{$_} = '' for @_requested;
152            
153             @_requested = grep { !/^STD$/i } keys %requested;
154            
155             warn sprintf "Following collection where requested to export: %s", join( ', ', @Data::Type::_requested) if $Data::Type::DEBUG;
156            
157             foreach ( 'STD', @_requested )
158             {
159             my $pm = $Data::Type::Collection::_arg_to_pkg->{$_};
160            
161             $pm = $_ unless defined $pm;
162            
163             use Data::Dump qw(pp);
164            
165             die "pm var empty for $_ out of args".Data::Dump::pp( 'STD', @_requested ) unless defined $pm;
166            
167             eval "use Data::Type::Collection::$pm;";
168            
169             if( $@ )
170             {
171             use Carp qw(cluck);
172            
173             use Data::Dump qw(dump);
174            
175             cluck "eval use Data::Type::Collection::$pm failed. Did you have a spelling mistake ? Requested where ".dump( @_requested, \%requested );
176            
177            
178             die $@;
179             }
180            
181             codegen( $_.'::' );
182             }
183            
184             @_ = @copy;
185            
186             __PACKAGE__->export_to_level(1, @_);
187             }
188            
189             package Data::Type::Entry;
190            
191             Class::Maker::class
192             {
193             public =>
194             {
195             bool => [qw( expected )],
196            
197             ref => [qw( object )],
198             },
199             };
200            
201             package Data::Type::L18N;
202            
203             use strict;
204            
205             use Locale::Maketext;
206            
207             our @ISA = qw( Locale::Maketext );
208            
209             package Data::Type::L18N::de;
210            
211             our @ISA = qw(Data::Type::L18N);
212            
213             use strict;
214            
215             use vars qw(%Lexicon);
216            
217             our %Lexicon =
218             (
219             __current_locale__ => 'deutsch',
220            
221             "Can't open file [_1]: [_2]\n" => "Problem beim öffnen der datei [_1]. Grund: [_2]\n",
222            
223             "You won!" => "Du hast gewonnen!",
224             );
225            
226             package Data::Type::L18N::en;
227            
228             our @ISA = qw(Data::Type::L18N);
229            
230             use strict;
231            
232             use vars qw(%Lexicon);
233            
234             our %Lexicon =
235             (
236             __current_locale__ => 'english',
237            
238             "Can't open file [_1]: [_2]\n" => "Can't open file [_1]: [_2]\n",
239            
240             "You won!" => "You won!",
241             );
242            
243             package Data::Type::Proxy;
244            
245             use vars qw($AUTOLOAD);
246            
247             sub AUTOLOAD
248             {
249             ( my $func = $AUTOLOAD ) =~ s/.*:://;
250            
251             return bless [ @_ ], Data::Type::_add_package_to_name( lc $func );
252             }
253            
254             #
255             # The universal "Data::Type::Object Interface"
256             #
257            
258             package Data::Type::Object::Interface;
259            
260             use Attribute::Util;
261            
262             sub desc : method
263             {
264             warn "abstract method called" if $Data::Type::DEBUG;
265            
266             return 'Universal';
267             }
268            
269             # static string
270            
271             sub info : Abstract method;
272            
273             # shell commando like usage
274            
275             sub usage { '' } #: Abstract method;
276            
277             sub _filters : method { () }
278            
279             # holds the logic of type validation. Should use Data::Type::ok()
280             # to dispatch public and private facets
281            
282             sub test
283             {
284             my $this = shift;
285            
286             $this->_load_dependency;
287            
288             Data::Type->filter( $this->_filters ) if scalar $this->_filters;
289            
290             return $this->_test( @_ );
291             }
292            
293             # return scalar/array/hash of alternativ choices when an inputfield
294             # is generated for this type
295            
296             sub choice : Abstract method;
297            
298             # returns a data structure used for the configuration/parameterization of
299             # the datatype
300            
301             sub param : Abstract method;
302            
303             # If some default value for C exists, they should be returned
304             # by this function
305            
306             sub default : Abstract method;
307            
308             # returns an array of required modules for this type
309             # [note] used to build a dependency tree
310            
311             sub basic_depends : method { qw() }
312            
313             sub _depends { () }
314            
315             sub depends : method
316             {
317             my $this = shift;
318            
319             my @d = ();
320            
321             @d = $this->_depends;
322            
323             return ( @d, $this->basic_depends );
324             }
325            
326             sub _load_dependency
327             {
328             my $this = shift;
329            
330             foreach ( $this->_depends )
331             {
332             unless( exists $Data::Type::_loaded->{$_} )
333             {
334             eval "use $_;"; die $@ if $@;
335            
336             $Data::Type::_loaded->{$_} = caller;
337             }
338             else
339             {
340             warn sprintf "%s tried to load twice %s", $_, join( ', ', caller ) if $Data::Type::DEBUG;
341             }
342             }
343             }
344            
345             # No idea ?
346            
347             sub to_text : Abstract method;
348            
349             # api for casting of types
350             # Usage: my $a_castedto_b = TYPE_A->cast( TYPE_B );
351             # [note] Ideally use C for dispatching
352            
353             sub cast : Abstract method;
354            
355             # return static text of some sort of "manpage" for this type
356            
357             sub doc : Abstract method; # A descriptive information about the interface should be placed here.
358            
359             # returns a scalar. This should be implemented by an Data::Type::Collection::*::Interface class
360             # which is then used when generating the final exportname with C
361            
362             sub prefix : method
363             {
364             Carp::croak "abstract method prefix called";
365             }
366            
367            
368             # return array of alias's for that type
369            
370             sub export : method
371             {
372             my $this = shift;
373            
374             $this ||= ref($this);
375            
376             my $name = Data::Type::_cut_package_from_name( $this );
377            
378             my $pre = $this->pkg_prefix;
379            
380             $name =~ s/^${pre}//gi;
381            
382             return ( $name );
383             }
384            
385             # return array of alias's for that type, including a prefix
386             # if this type is part of a collection
387            
388             sub exported : method
389             {
390             my $this = shift;
391            
392             my @result;
393            
394             foreach( $this->export )
395             {
396             my $n = $this->prefix().$_;
397            
398             $Data::Type::_reformat_name->( $n ) if defined $Data::Type::_reformat_name;
399            
400             push @result, Data::Type::_gen_name( $n );
401             }
402            
403             return @result;
404             }
405            
406             sub summary
407             {
408             my $this = shift;
409            
410             if( wantarray )
411             {
412             return Data::Type::summary( scalar @_ ? @_ : '' , $this );
413             }
414            
415             my $sum;
416            
417             foreach my $entry ( Data::Type::summary( scalar @_ ? @_ : '' , $this ) )
418             {
419             $sum .= Data::Type::sprintfln "expecting it %s %s ", $entry->expected ? 'is' : 'is NOT', Data::Type::strlimit( $entry->object->info() );
420             }
421            
422             return $sum;
423             }
424            
425             use String::ExpandEscapes;
426            
427             sub pod : method
428             {
429             my $this = shift;
430            
431             my $href = shift;
432            
433             my $escapes = {
434             e => join( ', ', $this->exported ),
435             d => $this->desc,
436             v => $this->VERSION || 'undefined',
437             u => $this->usage,
438             m => join(', ', map { "L<$_>" } $this->_depends),
439             };
440            
441             my @fields;
442            
443             push @fields, '=head2 %e (since %v)', '%d';
444            
445             if( $this->usage || $this->_depends )
446             {
447             # $escapes->{i} = $this->info and push @fields, '%i' if $this->info;
448            
449             # $escapes->{s} = $this->summary and push @fields, '=item SUMMARY', '%s' if $this->summary;
450            
451             $escapes->{f} = join '; ', ( map { my $f = shift @$_; scalar @$_ ? "L<$f|Data::Type::Filter/$f> ".join( ', ', @$_ ) : () } $this->_filters ) and push @fields, '=head3 Filters', '%f' if $this->_filters;
452            
453             push @fields, '=head3 Usage', '%u' if $escapes->{u};
454            
455             push @fields, '=head3 Depends', '%m' if $escapes->{m};
456             }
457            
458             my $fmt = join ( "\n\n", @fields )."\n\n";
459            
460             my ($result, $error) = String::ExpandEscapes::expand( $fmt, $escapes );
461            
462             Carp::croak "Illegal escape sequence $error\n" if $error;
463            
464             return $result;
465             }
466            
467             package Data::Type::Context;
468            
469             Class::Maker::class
470             {
471             public =>
472             {
473             int => [qw( failed passed )],
474            
475             scalar => [qw( value )],
476            
477             array => [qw( types )],
478             },
479             };
480            
481             package Data::Type;
482            
483             # See head of file for $VERSION variable (moved because of bug in VERSION_FROM of Makefile.pl)
484            
485             # This value is important. It gets reset to undef in valid() before the test starts. During test
486             # it hold the $value of the data to tested against.
487            
488             our $value;
489            
490             our @_history;
491            
492             our %_alias; # holds alias names for type like $_alias{BIO::CODON} = 'codon';
493            
494             no strict 'refs';
495            
496             our @_locale_handles = ( 'en' );
497            
498             our $_lh = Data::Type::L18N->get_handle( @_locale_handles ) || die "What language?";
499            
500             sub lh { $_lh }
501            
502             use Data::Type::Exception;
503            
504             use Data::Type::Filter;
505            
506             use Data::Type::Facet;
507            
508             # generate Type subs
509            
510             sub current_locale
511             {
512             my $this = shift;
513            
514             return $_lh->maketext('__current_locale__');
515             }
516            
517             sub set_locale : method
518             {
519             my $this = shift;
520            
521             $Data::Type::_lh = Data::Type::L18N->get_handle( @_ ) || die "Locale not implented or found";
522             }
523            
524             sub esc ($) { my $cpy = $_[0] || '' ; $cpy =~ s/\n/\\n/; "'".$cpy."'" }
525            
526             sub strlimit
527             {
528             my $limit = $_[1] || 60;
529            
530             return length( $_[0] ) > $limit ? join('', (split(//, $_[0]))[0..$limit-1]).'..' : $_[0];
531             }
532            
533             sub filter : method
534             {
535             my $this = shift;
536            
537             foreach ( @_ )
538             {
539             my ( $name, @args ) = @{$_};
540            
541             print " " x 2;
542            
543             my $before = $Data::Type::value;
544            
545             "Data::Type::Filter::${name}"->filter( @args );
546            
547             print " " x 2;
548            
549             printf '%-20s %20s(%s) %30s => %-30s', 'FILTER', $name, join(',',@args), esc( $before), esc( $Data::Type::value) if $Data::Type::DEBUG;
550            
551             print "\n";
552             }
553             }
554            
555             # Generate Type alias subs
556             #
557             # - Generate subs like 'VARCHAR' into this package
558             # - These are then Exported
559             #
560             # Note that codegen is called above
561            
562             sub _gen_name
563             {
564             my $what = shift;
565            
566             return $Data::Type::_master_prefix.uc( $what );
567             }
568            
569             sub _add_package_to_name
570             {
571             my $name = shift || die "_add_package_to_name needs at least one parameter";
572            
573             return 'Data::Type::Object::'.$name;
574             }
575            
576             sub _cut_package_from_name
577             {
578             my $p = shift || die "_cut_package_from_name needs at least one parameter";
579            
580             return ( $p =~ /^Data::Type::Object::([^:]+)/ )[0] || die "'$p' not matchable by _cut_package_from_name";
581             }
582            
583             sub _revert_alias
584             {
585            
586             return exists $_alias{ shift } ? $_alias{ shift } : undef;
587             }
588            
589             sub _translate
590             {
591             my $name = shift;
592            
593             return join ', ', $name->exported;
594             }
595            
596             sub expect
597             {
598             my $recording = shift;
599            
600             my $expected = shift;
601            
602             foreach my $that ( @_ )
603             {
604             $that = bless [ $that ], 'Data::Type::Facet::__anon' if ref($that) eq 'CODE';
605            
606             if ( $recording )
607             {
608             push @Data::Type::_history, Data::Type::Entry->new( object => $that, expected => $expected );
609             }
610             else
611             {
612             Data::Type::try
613             {
614             $that->test;
615             }
616             catch Error Data::Type::with
617             {
618             throw Data::Type::Exception( value => $Data::Type::value, type => $that, catched => \@_ ) if $expected;
619             };
620             }
621             }
622             }
623            
624             our $record = 0;
625            
626             sub ok { expect( $record, @_ ) }
627            
628             sub assert { println $_[0] ? '..ok' : '..nok'}
629            
630             # Tests Types
631            
632             sub valid
633             {
634             $Data::Type::value = ( @_ > 1 ) ? shift : $_;
635            
636             my $type = shift;
637            
638             printf "%-20s %30s %-60s\n", 'VALID', esc( $Data::Type::value ), $type if $Data::Type::DEBUG;
639            
640             die "usage: valid( VALUE, TYPE )" if @_;
641            
642             printfln "\n\nTesting %s given '%s' (%s)", ( $type->exported )[0], $value, strlimit( $type->info ) if $Data::Type::DEBUG;
643            
644             $type->test;
645             }
646            
647             # Wrapper for dieing instead of throwing exceptions
648            
649             our @err;
650            
651             sub dvalid
652             {
653             my @args = @_;
654            
655             @err = ();
656            
657             Data::Type::try
658             {
659             $Data::Type::value = ( @args > 1 ) ? shift @args : $_;
660            
661             my $type = shift @args;
662            
663             printf "%-20s %30s %-60s\n", 'DVALID', $Data::Type::value, $type if $Data::Type::DEBUG;
664            
665             die "usage: dvalid( $Data::Type::value, $type )" if @args;
666            
667             printfln "\n\nTesting %s given '%s' (%s)", ( $type->exported )[0], $Data::Type::value, strlimit( $type->info ) if $Data::Type::DEBUG;
668            
669             $type->test;
670             }
671             catch Error Data::Type::with
672             {
673             @err = @_;
674             };
675            
676             return @err ? 0 : 1;
677             }
678            
679             sub is { &dvalid }
680            
681             sub isnt { not &is }
682            
683             sub summary
684             {
685             @Data::Type::_history = ();
686            
687             $Data::Type::record = 1;
688            
689             $Data::Type::value = shift;
690            
691             #print Data::Dumper->Dump( [ \@_ ] );
692            
693             $_->test for @_;
694            
695             $Data::Type::record = 0;
696            
697             return @Data::Type::_history;
698             }
699            
700             sub _search_pkg
701             {
702             my $path = '';
703            
704             my @found;
705            
706             no strict 'refs';
707            
708             foreach my $pkg ( @_ )
709             {
710             next unless $pkg =~ /::$/;
711            
712             $path .= $pkg;
713            
714             if( $path =~ /(.*)::$/ )
715             {
716             foreach my $symbol ( sort keys %{$path} )
717             {
718             if( $symbol =~ /(.+)::$/ && $symbol ne 'main::' )
719             {
720             push @found, "${path}$1";
721             }
722             }
723             }
724             }
725            
726             return @found;
727             }
728            
729             sub type_list_as_packages { map { die if $_ =~ /Interface/; $_ } grep { $_ ne 'Data::Type::Object::Interface' and $_->isa( 'Data::Type::Object::Interface' ) } _search_pkg( 'Data::Type::Object::' ) }
730            
731             sub type_list { map { _cut_package_from_name($_) } type_list_as_packages() }
732            
733             sub filter_list_as_packages { grep { $_ ne 'Data::Type::Filter::Interface' and $_->isa( 'Data::Type::Filter::Interface' ) } _search_pkg( 'Data::Type::Filter::' ) }
734            
735             sub filter_list { filter_list_as_packages() }
736            
737             sub facet_list_as_packages { grep { $_ ne 'Data::Type::Facet::Interface' and $_->isa( 'Data::Type::Facet::Interface' ) } _search_pkg( 'Data::Type::Facet::' ) }
738            
739             sub facet_list { facet_list_as_packages() }
740            
741             sub l18n_list { map { /::([^:]+)$/; uc $1 } _search_pkg( 'Data::Type::L18N::' ) }
742            
743             sub _show_list
744             {
745             my $hash = shift;
746            
747             my $ind = shift || 2;
748            
749             my $result;
750            
751             foreach my $key (keys %$hash)
752             {
753             my $val = $hash->{ $key };
754            
755             # headlines
756            
757             unless( ref( $key ) )
758             {
759             $result .= sprintf qq|%s"%s"\n|, " " x $ind, $key;
760             }
761             else
762             {
763             $result .= sprintf qq|%s"%s"\n|, " " x $ind, $_ for @$key;
764             }
765            
766             # contents
767            
768             if( ref( $val ) eq 'ARRAY' )
769             {
770             $result .= sprintf "\n%s %s\n\n", " " x $ind, join( ', ', sort { $a cmp $b } @$val );
771             }
772             elsif( ref( $val ) eq 'HASH' )
773             {
774             $result .= _show_list( $val, $ind + 2 );
775             }
776             }
777            
778             return $result;
779             }
780            
781             sub _unique_ordered
782             {
783             my $prev = shift;
784            
785             my @result = ( $prev );
786            
787             for ( iter \@_ )
788             {
789             push @result, VALUE() if VALUE() ne $prev;
790            
791             $prev = $_;
792             }
793            
794             return @result;
795             }
796            
797             sub toc
798             {
799             return '';
800            
801             my $result;
802            
803             use Tie::ListKeyedHash;
804            
805             tie my %tied_hash, 'Tie::ListKeyedHash';
806            
807             foreach my $pkg_name ( type_list_as_packages() )
808             {
809             warn "$pkg_name will be reflected" if $Data::Type::DEBUG;
810            
811             my @isa = _unique_ordered @{ Class::Maker::Reflection::inheritance_isa( @{ $pkg_name.'::ISA' } ) };
812            
813             # this is brute and could become a trouble origin
814            
815             @isa = grep { $_ ne 'Data::Type::Object::Interface' and $_->isa( 'Data::Type::Object::Interface' ) } @isa;
816            
817             Carp::croak "$pkg_name has invalid isa tree with @isa" unless @isa;
818            
819             my $special_key = [ _unique_ordered map { $_->can( 'desc' ) ? $_->desc : () } @isa ];
820            
821             print Data::Dumper->Dump( [ \@isa, $special_key ] ); # if $Data::Type::DEBUG;
822            
823             $tied_hash{ $special_key } = [] unless defined $tied_hash{ $special_key };
824            
825             push @{ $tied_hash{ $special_key } }, sprintf( '%s', _translate( $pkg_name ) );
826             }
827            
828             $result .= _show_list \%tied_hash;
829            
830             return $result;
831             }
832            
833             # look at sub _export below. Normally it is used to use ie. DB_VARCHAR instead of DB::VARCHAR (namespace pollution!).
834            
835             our $_reformat_name = undef;
836            
837             sub _export
838             {
839             my $what = shift;
840            
841             foreach my $where ( @_ )
842             {
843             my $c = sprintf "sub %s { Data::Type::Proxy::%s( \@_ ); };", $where, $what;
844            
845             println "_export: $c" if $Data::Type::DEBUG;
846            
847             eval $c;
848            
849             die $@ if $@;
850             }
851             }
852            
853             sub codegen
854             {
855             my $prefix = shift;
856            
857             warn "codegen for prefix $prefix" if $Data::Type::DEBUG;
858            
859             my @aliases;
860            
861             foreach my $type ( Data::Type::type_list() )
862             {
863             printfln "generating code for %s", $type if $Data::Type::DEBUG;
864            
865             my $p = _add_package_to_name($type)->prefix;
866            
867             warnfln "codegen if $p eq $prefix" if $Data::Type::DEBUG > 1;
868            
869             printfln "export if $p eq $prefix." if $Data::Type::DEBUG;
870            
871             if( $p eq $prefix )
872             {
873             printfln "exporting %S", $type if $Data::Type::DEBUG;
874            
875             _export( $type, _add_package_to_name($type)->exported );
876            
877             my @n = _add_package_to_name($type)->exported;
878            
879             push @aliases, @n;
880            
881             $_alias{$_} = $type for @n;
882             }
883             }
884            
885             if( @aliases )
886             {
887             warnfln sprintf "eval: use subs qw(%s);", join ' ', @aliases if $Data::Type::DEBUG;
888            
889             eval sprintf "use subs qw(%s);", join ' ', @aliases;
890            
891             warn $@ if $@;
892             }
893             }
894            
895             1;
896            
897             __END__