File Coverage

blib/lib/Mock/Data/Util.pm
Criterion Covered Total %
statement 58 63 92.0
branch 45 54 83.3
condition 5 6 83.3
subroutine 11 14 78.5
pod 7 7 100.0
total 126 144 87.5


line stmt bran cond sub pod time code
1             package Mock::Data::Util;
2 12     12   1069137 use Exporter::Extensible -exporter_setup => 1;
  12         111623  
  12         96  
3             export(qw(
4             uniform_set weighted_set inflate_template coerce_generator mock_data_subclass
5             charset template _parse_context _escape_str _dump
6             ));
7             require Scalar::Util;
8             require Carp;
9             our @CARP_NOT= qw( Mock::Data Mock::Data::Generator );
10              
11             # ABSTRACT: Exportable functions to assist with declaring mock data
12             our $VERSION = '0.04'; # VERSION
13              
14              
15             sub uniform_set {
16 0     0 1 0 return Mock::Data::Set->new(items => [@_]);
17             }
18              
19             sub weighted_set {
20 0     0 1 0 my $i= 0;
21 0         0 return Mock::Data::Set->new_weighted(@_);
22             }
23              
24             sub charset {
25 20     20 1 107 return Mock::Data::Charset->new(@_);
26             }
27              
28              
29             sub template {
30 0     0 1 0 Mock::Data::Template->new(@_);
31             }
32              
33             sub inflate_template {
34 31     31 1 292935 my ($tpl)= @_;
35             # If it does not contain '{', return as-is. Else parse.
36 31 100       147 return $tpl if index($tpl, '{') == -1;
37 18         17 local $@;
38 18         22 my $cmp= eval { Mock::Data::Template->new($tpl) };
  18         50  
39             # If the template "compiled" to a simple scalar, return the scalar. Else return the generator.
40 18 100       108 return !$cmp? $tpl : ref $cmp->{_compiled}? $cmp : $cmp->{_compiled};
    100          
41             }
42              
43              
44             sub coerce_generator {
45 164     164 1 237607 my ($spec)= @_;
46 164 50       1200 !defined $spec? Carp::croak("Can't coerce undef to a generator")
    100          
    100          
    50          
    100          
    100          
    50          
47             : !ref $spec? Mock::Data::Template->new($spec)
48             : ref $spec eq 'ARRAY'? Mock::Data::Set->new(items => [map &_maybe_coerce_set_item, @$spec])
49             : ref $spec eq 'HASH'? weighted_set(%$spec)
50             : ref $spec eq 'CODE'? Mock::Data::GeneratorSub->new($spec)
51             : ref($spec)->can('generate')? $spec
52             : ref $spec eq 'Regexp'? Mock::Data::Regex->new($spec)
53             : Carp::croak("Don't know how to make '$spec' into a generator");
54             }
55             sub _maybe_coerce_set_item {
56 14 100   14   73 !ref($_)? inflate_template($_)
    100          
57             : ref($_) eq 'ARRAY'? Mock::Data::Set->new(items => [map &_maybe_coerce_set_item, @$_])
58             : coerce_generator($_);
59             }
60              
61              
62             sub mock_data_subclass {
63 13     13 1 223659 my $self= shift;
64 13   66     69 my $class= ref $self || $self;
65 13         249 my @to_add= grep !$class->isa($_), @_;
66             # Nothing to do if already part of this class/object
67 13 50       46 return $self unless @to_add;
68             # Determine what the new @ISA will be
69             my @new_isa= defined $Mock::Data::auto_subclasses{$class}
70 13 50       49 ? @{$Mock::Data::auto_subclasses{$class}}
  0         0  
71             : ($class);
72             # Remove redundant classes
73 13         33 for my $next_class (@to_add) {
74 26 100       192 next if grep $_->isa($next_class), @new_isa;
75 24         244 @new_isa= grep !$next_class->isa($_), @new_isa;
76 24         54 push @new_isa, $next_class;
77             }
78             # If only one class remains, then this one class already defined an inheritance for all
79             # the others. Use it directly.
80 13         21 my $new_class;
81 13 100       32 if (@new_isa == 1) {
82 3         6 $new_class= $new_isa[0];
83             } else {
84             # Now find if this combination was already composed, else create it.
85 10         31 $new_class= _name_for_combined_isa(@new_isa);
86 10 100       91 if (!$Mock::Data::auto_subclasses{$new_class}) {
87 12     12   10169 no strict 'refs';
  12         24  
  12         9628  
88 8         15 @{"${new_class}::ISA"}= @new_isa;
  8         252  
89 8         53 $Mock::Data::auto_subclasses{$new_class}= \@new_isa;
90             }
91             }
92 13 100       71 return ref $self? bless($self, $new_class) : $new_class;
93             }
94              
95             # When choosing a name for a new @ISA list, the name could be something as simple as ::AUTO$n
96             # with an incrementing number, but that wouldn't be helpful in a stack dump. But, a package
97             # name fully containing the ISA package names could get really long and also be unhelpful.
98             # Compromise by shortening the names by removing Mock::Data prefix and removing '::' and '_'.
99             # If this results in a name collision (seems unlikely), add an incrementing number on the end.
100             sub _name_for_combined_isa {
101 10     10   21 my @parts= grep { $_ ne 'Mock::Data' } @_;
  24         60  
102 10         31 my $isa_key= join "\0", @parts;
103 10         20 for (@parts) {
104 18         58 $_ =~ s/^Mock::Data:://;
105 18         79 $_ =~ s/::|_//g;
106             }
107 10         26 my $class= join '_', 'Mock::Data::_AUTO', @parts;
108 10         15 my $iter= 0;
109 10         18 my $suffix= '';
110             # While iterating, check to see if that package uses the same ISA list as this new request.
111 10   100     47 while (defined $Mock::Data::auto_subclasses{$class . $suffix}
112             && $isa_key ne join("\0",
113 17         49 grep { $_ ne 'Mock::Data' } @{$Mock::Data::auto_subclasses{$class . $suffix}}
  6         18  
114             )
115             ) {
116 4         17 $suffix= '_' . ++$iter;
117             }
118 10         37 $class . $suffix;
119             }
120              
121             # For those cases where Data::Dumper is both overkill and insufficient...
122             my %_escape_common= ( "\n" => '\n', "\t" => '\t', "\0" => '\0' );
123             sub _escape_str {
124 212     212   2502 my $str= shift;
125 212 100       788 $str =~ s/([^\x20-\x7E])/ $_escape_common{$1} || sprintf("\\x{%02X}",ord $1) /ge;
  79         543  
126 212         1386 return $str;
127             }
128             sub _dump;
129             sub _dump {
130 67 100   67   24005 local $_= shift if @_;
131             !defined $_? 'undef'
132             : !ref $_? (Scalar::Util::looks_like_number($_)? $_ : '"'._escape_str($_).'"')
133             : ref $_ eq 'ARRAY'? '['.join(', ', map _dump, @$_).']'
134 67 100       435 : ref $_ eq 'HASH'? do {
    50          
    100          
    100          
    50          
135 19         28 my $hash= $_;
136             '{'.join(', ', map {
137 19         71 ($_ =~ /^\w+\z/? $_ : '"'._escape_str($_).'"')
138 23 50       136 .' => '._dump($hash->{$_})
139             } sort keys %$hash).'}';
140             }
141             : "$_"
142             }
143             sub _parse_context {
144 2 50   2   8 return '"' . _escape_str(substr($_, defined $_[0]? $_[0] : pos, 10)) .'"';
145             }
146              
147             # included last, because they depend on this module.
148             require Mock::Data::Set;
149             require Mock::Data::Charset;
150             require Mock::Data::Regex;
151             require Mock::Data::Template;
152             require Mock::Data::GeneratorSub;
153              
154             __END__