File Coverage

blib/lib/DBIx/SQLEngine/Utility/CloneWithParams.pm
Criterion Covered Total %
statement 54 61 88.5
branch 22 24 91.6
condition n/a
subroutine 8 9 88.8
pod 2 2 100.0
total 86 96 89.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             DBIx::SQLEngine::Utility::CloneWithParams - Nifty Cloner
4              
5             =head1 SYNOPSIS
6              
7             use DBIx::SQLEngine::Utility::CloneWithParams;
8            
9             $clone = clone_with_parameters( $string, @replacements );
10             $clone = clone_with_parameters( \@array, @replacements );
11             $clone = clone_with_parameters( \%hash, @replacements );
12              
13             =head1 DESCRIPTION
14              
15             This package provides a function named clone_with_parameters() that makes deep copies of nested data structures, while making replacements in key places.
16              
17             =head2 clone_with_parameters
18              
19             $clone = clone_with_parameters( $reference, @replacements );
20              
21             This function makes deep copies of nested data structures, with object
22             reblessing and loop detection to avoid endless cycles. (The internals are
23             based on clone() from L.)
24              
25             It's one distinctive behavior is that if a data structure contains references
26             to the special numeric Perl variables $1, $2, $3, and so forth, when it is
27             cloned they are replaced with a set of provided parameter values. It also
28             replaces stringified versions of those references embedded in scalar values.
29              
30             An exception is thrown if the number of parameters provided does not match
31             the number of special variables referred to.
32              
33             B
34              
35             =over 2
36              
37             =item *
38              
39             This will not properly copy tied data.
40              
41             =item *
42              
43             Using this to clone objects will only work with simple objects that don't
44             do much preprocessing of the values they contain.
45              
46             =back
47              
48             B
49              
50             =over 2
51              
52             =item *
53              
54             Here's a simple copy of a string with embedded values to be provided by the caller:
55              
56             my $template = \$1 . '-' . \$2;
57             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
58             ok( $clone, 'Foozle-Basil' );
59              
60             =item *
61              
62             Here's a simple cloning of an array with values to be provided by the caller:
63              
64             my $template = [ \$1, '-', \$2 ];
65             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
66             is_deeply( $clone, [ 'Foozle', '-', 'Basil' ] );
67              
68             =item *
69              
70             Here's a simple cloning of a hash with key values to be provided by the caller:
71              
72             my $template = { foo => \$1, bar => \$2 };
73             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
74             is_deeply( $clone, { foo => 'Foozle', bar => 'Basil' } );
75              
76             =item *
77              
78             Templates to be copied can contain nested data structures, and can use paramters multiple times:
79              
80             my $template = { foo => \$1, bar => [ \$2, 'baz', \$2 ] };
81             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
82             is_deeply( $clone, { foo=>'Foozle', bar=>['Basil','baz','Basil'] } );
83              
84             =item *
85              
86             Although hash keys are automatically stringified, they still are substituted:
87              
88             my $template = { foo => 'bar', \$1 => \$2 };
89             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
90             is_deeply( $clone, { foo => 'bar', Foozle => 'Basil' } );
91              
92             =item *
93              
94             Objects can be copied to produce properly-blessed clones:
95              
96             package My::SimpleObject;
97              
98             sub new { my $class = shift; bless { @_ } $class }
99             sub foo { ( @_ == 1 ) ? $_[0]->{foo} : ( $_[0]->{foo} = $_[1] ) }
100             sub bar { ( @_ == 1 ) ? $_[0]->{bar} : ( $_[0]->{bar} = $_[1] ) }
101              
102             package main;
103             use DBIx::SQLEngine::Utility::CloneWithParams;
104              
105             my $template = My::SimpleObject->new( foo => \$1, bar => \$2 );
106             my $clone = clone_with_parameters( $template, 'Foozle', 'Basil' );
107             isa_ok( $clone, 'My::SimpleObject' );
108             ok( $clone->foo, 'Foozle' );
109             ok( $clone->bar, 'Basil' );
110              
111             If the class itself imports clone_with_parameters(), it can be called as a method instead of a function:
112              
113             package My::SimpleObject;
114             use DBIx::SQLEngine::Utility::CloneWithParams;
115             ...
116            
117             package main;
118              
119             my $template = My::SimpleObject->new( foo => \$1, bar => \$2 );
120             my $clone = $template->clone_with_parameters( 'Foozle', 'Basil' );
121             ...
122              
123             =back
124              
125             =cut
126              
127             ########################################################################
128              
129             package DBIx::SQLEngine::Utility::CloneWithParams;
130              
131 1     1   16278 use Exporter;
  1         2  
  1         197  
132 1     1   4016 sub import { goto &Exporter::import }
133             @EXPORT = @EXPORT_OK = qw( clone_with_parameters safe_eval_with_parameters );
134             %EXPORT_TAGS = ( all => \@EXPORT_OK );
135              
136 1     1   7 use strict;
  1         2  
  1         37  
137 1     1   5 use Carp;
  1         2  
  1         211  
138              
139             ########################################################################
140              
141             my @num_refs = map { \$_ } ( $1, $2, $3, $4, $5, $6, $7, $8, $9 );
142             my $num_refs = join "|", map { "\Q$_" } @num_refs;
143             my %num_refs = map { $num_refs[ $_ -1 ] => $_ } ( 1 .. 9 );
144              
145             ########################################################################
146              
147 1     1   6 use vars qw( %CopiedItems @Parameters @ParametersUsed );
  1         2  
  1         854  
148              
149             # $deep_copy = clone_with_parameters( $value_or_ref );
150             sub clone_with_parameters {
151 21     21 1 1439 my $item = shift;
152 21         54 local @Parameters = @_;
153 21         32 local %CopiedItems = ();
154 21         33 local @ParametersUsed = ();
155 21         39 my $clone = __clone_with_parameters( $item );
156 20 100       88 if ( scalar @ParametersUsed < scalar @Parameters ) {
157 1         166 confess( "Too many arguments: " . scalar(@Parameters) .
158             " instead of " . scalar(@ParametersUsed));
159             }
160 19         100 return $clone;
161             }
162              
163             sub __get_parameter {
164 24     24   27 my $placeholder = shift;
165            
166 24 100       52 if ( $placeholder > scalar @Parameters ) {
167 1         319 confess( "Too few arguments: " . scalar(@Parameters) .
168             " instead of $placeholder");
169             }
170 23         40 $ParametersUsed[ $placeholder -1 ] ++;
171 23         87 return $Parameters[ $placeholder -1 ];
172             }
173              
174             # $copy = __clone_with_parameters( $value_or_ref );
175             sub __clone_with_parameters {
176 59     59   76 my $source = shift;
177            
178 59 100       157 return $CopiedItems{ $source } if ( exists $CopiedItems{ $source } );
179              
180 58 100       158 if ( my $placeholder = $num_refs{ $source } ) {
181 20         33 return __get_parameter( $placeholder );
182             }
183            
184 38         78 my $ref_type = ref $source;
185 38 100       71 if (! $ref_type) {
186 22         164 $source =~ s/($num_refs)/ __get_parameter( $num_refs{ $1 } ) /geo;
  4         14  
187 22         70 return $source;
188             }
189            
190 16         16 my $class_name;
191 16 100       276 if ( "$source" =~ /^\Q$ref_type\E\=([A-Z]+)\(0x[0-9a-f]+\)$/ ) {
192 1         4 $class_name = $ref_type;
193 1         3 $ref_type = $1;
194             }
195            
196 16         25 my $copy;
197 16 100       55 if ($ref_type eq 'SCALAR') {
    100          
    100          
    100          
198 2         8 $CopiedItems{ $source } = $copy = \( my $var = "" );;
199 2         6 $$copy = __clone_with_parameters($$source);
200             } elsif ($ref_type eq 'REF') {
201 1         5 $CopiedItems{ $source } = $copy = \( my $var = "" );;
202 1         4 $$copy = __clone_with_parameters($$source);
203             } elsif ($ref_type eq 'HASH') {
204 6         22 $CopiedItems{ $source } = $copy = {};
205 6         18 %$copy = map { __clone_with_parameters($_) } %$source;
  22         41  
206             } elsif ($ref_type eq 'ARRAY') {
207 6         19 $CopiedItems{ $source } = $copy = [];
208 6         12 @$copy = map { __clone_with_parameters($_) } @$source;
  13         25  
209             } else {
210 1         2 $copy = $source;
211             }
212            
213 16 100       37 bless $copy, $class_name if $class_name;
214            
215 16         38 return $copy;
216             }
217              
218             ########################################################################
219              
220             =head2 safe_eval_with_parameters
221              
222             @results = safe_eval_with_parameters( $perl_code_string );
223              
224             Uses the Safe package to eval the provided code string. Uses a compartment which shares the same numeric variables, so that values evaluated this way can then be cloned with clone_with_parameters.
225              
226             =cut
227              
228             my $safe_compartment;
229             sub safe_eval_with_parameters {
230 0 0   0 1   $safe_compartment or $safe_compartment = do {
231 0           require Safe;
232 0           my $compartment = Safe->new();
233 0           $compartment->share_from( 'main', [ map { '$' . $_ } ( 1 .. 9 ) ] );
  0            
234 0           $compartment;
235             };
236              
237 0           $safe_compartment->reval( shift );
238             }
239              
240             ########################################################################
241              
242             =head1 SEE ALSO
243              
244             See L for the overall interface and developer documentation.
245              
246             See L for general information about
247             this distribution, including installation and license information.
248              
249             =cut
250              
251             ########################################################################
252              
253             1;