File Coverage

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


line stmt bran cond sub pod time code
1             package Mock::Data::Util;
2 12     12   1118645 use Exporter::Extensible -exporter_setup => 1;
  12         87683  
  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.03'; # 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 14     14 1 46 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 14774 my ($tpl)= @_;
35             # If it does not contain '{', return as-is. Else parse.
36 31 100       142 return $tpl if index($tpl, '{') == -1;
37 18         31 local $@;
38 18         32 my $cmp= eval { Mock::Data::Template->new($tpl) };
  18         57  
39             # If the template "compiled" to a simple scalar, return the scalar. Else return the generator.
40 18 100       98 return !$cmp? $tpl : ref $cmp->{_compiled}? $cmp : $cmp->{_compiled};
    100          
41             }
42              
43              
44             sub coerce_generator {
45 162     162 1 12936 my ($spec)= @_;
46 162 0       797 !defined $spec? Carp::croak("Can't coerce undef to a generator")
    50          
    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   56 !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 16688 my $self= shift;
64 13   66     51 my $class= ref $self || $self;
65 13         127 my @to_add= grep !$class->isa($_), @_;
66             # Nothing to do if already part of this class/object
67 13 50       33 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       37 ? @{$Mock::Data::auto_subclasses{$class}}
  0         0  
71             : ($class);
72             # Remove redundant classes
73 13         26 for my $next_class (@to_add) {
74 26 100       91 next if grep $_->isa($next_class), @new_isa;
75 24         119 @new_isa= grep !$next_class->isa($_), @new_isa;
76 24         45 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         19 my $new_class;
81 13 100       25 if (@new_isa == 1) {
82 3         7 $new_class= $new_isa[0];
83             } else {
84             # Now find if this combination was already composed, else create it.
85 10         19 $new_class= _name_for_combined_isa(@new_isa);
86 10 100       28 if (!$Mock::Data::auto_subclasses{$new_class}) {
87 12     12   7952 no strict 'refs';
  12         26  
  12         8659  
88 8         13 @{"${new_class}::ISA"}= @new_isa;
  8         193  
89 8         35 $Mock::Data::auto_subclasses{$new_class}= \@new_isa;
90             }
91             }
92 13 100       89 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   17 my @parts= grep { $_ ne 'Mock::Data' } @_;
  24         53  
102 10         25 my $isa_key= join "\0", @parts;
103 10         19 for (@parts) {
104 18         94 $_ =~ s/^Mock::Data:://;
105 18         71 $_ =~ s/::|_//g;
106             }
107 10         38 my $class= join '_', 'Mock::Data::_AUTO', @parts;
108 10         15 my $iter= 0;
109 10         17 my $suffix= '';
110             # While iterating, check to see if that package uses the same ISA list as this new request.
111 10   100     37 while (defined $Mock::Data::auto_subclasses{$class . $suffix}
112             && $isa_key ne join("\0",
113 17         69 grep { $_ ne 'Mock::Data' } @{$Mock::Data::auto_subclasses{$class . $suffix}}
  6         14  
114             )
115             ) {
116 4         14 $suffix= '_' . ++$iter;
117             }
118 10         29 $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   2054 my $str= shift;
125 212 100       676 $str =~ s/([^\x20-\x7E])/ $_escape_common{$1} || sprintf("\\x{%02X}",ord $1) /ge;
  94         492  
126 212         1356 return $str;
127             }
128             sub _dump;
129             sub _dump {
130 67 100   67   30422 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       546 : ref $_ eq 'HASH'? do {
    50          
    100          
    100          
    50          
135 19         31 my $hash= $_;
136             '{'.join(', ', map {
137 19         83 ($_ =~ /^\w+\z/? $_ : '"'._escape_str($_).'"')
138 23 50       172 .' => '._dump($hash->{$_})
139             } sort keys %$hash).'}';
140             }
141             : "$_"
142             }
143             sub _parse_context {
144 2 50   2   30 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__