File Coverage

blib/lib/Data/Hopen/Scope.pm
Criterion Covered Total %
statement 110 116 94.8
branch 37 48 77.0
condition 22 25 88.0
subroutine 28 32 87.5
pod 8 8 100.0
total 205 229 89.5


line stmt bran cond sub pod time code
1             # Data::Hopen::Scope - a nested key-value store
2             package Data::Hopen::Scope;
3 18     18   808 use strict;
  18         37  
  18         561  
4 18     18   97 use Data::Hopen::Base;
  18         34  
  18         158  
5 18     18   3926 use Exporter 'import';
  18         39  
  18         631  
6 18     18   116 use Scalar::Util qw(refaddr);
  18         43  
  18         1525  
7              
8             our $VERSION = '0.000017';
9              
10             # Class definition
11             use Class::Tiny {
12 18         171 outer => undef,
13             local => false,
14             name => 'anonymous scope',
15             merge_strategy => undef,
16              
17             # Internal
18             _first_set => undef, # name of the first set
19             _merger_instance => undef, # A Hash::Merge instance
20 18     18   9228 };
  18         33690  
21              
22             # Static exports
23 18     18   19021 use vars::i '@EXPORT_OK_PUBLIC' => [qw(is_first_only)];
  18         4977  
  18         151  
24             use vars::i {
25 18         147 '@EXPORT' => [qw(FIRST_ONLY)],
26             '@EXPORT_OK' => [@EXPORT_OK_PUBLIC, qw(_set0)],
27 18     18   2021 };
  18         54  
28 18         178 use vars::i '%EXPORT_TAGS' => {
29             'default' => [@EXPORT],
30             'all' => [@EXPORT, @EXPORT_OK_PUBLIC],
31             'internal' => [qw(_set0)],
32 18     18   2540 };
  18         43  
33              
34             my $_first_only = {};
35 4     4 1 106 sub FIRST_ONLY { $_first_only }
36              
37 18     18   2287 use constant _LOCAL => 'local';
  18         61  
  18         1309  
38              
39             # What we use
40 18     18   108 use Config;
  18         87  
  18         1049  
41 18     18   2306 use Data::Hopen qw(getparameters);
  18         48  
  18         1178  
42 18     18   8976 use Data::Hopen::Util::Data qw(clone forward_opts);
  18         46  
  18         1223  
43 18     18   8899 use Hash::Merge;
  18         56697  
  18         836  
44 18     18   9528 use POSIX ();
  18         118305  
  18         609  
45 18     18   7902 use Set::Scalar;
  18         190924  
  18         944  
46 18     18   8862 use Sub::ScopeFinalizer qw(scope_finalizer);
  18         9333  
  18         26739  
47              
48             # Docs {{{1
49              
50             =head1 NAME
51              
52             Data::Hopen::Scope - a nested key-value store.
53              
54             =head1 SYNOPSIS
55              
56             A Scope represents a set of data available to operations. It is a
57             key-value store that falls back to an outer C if a requested key
58             isn't found.
59              
60             This class is the abstract base of Scopes. See L
61             for an example of a concrete implementation using a hash under the
62             hood. Different subclasses use different representations.
63             See L for more on that topic.
64              
65             =head1 STATIC EXPORTS
66              
67             =head2 FIRST_ONLY
68              
69             A flag used as a L (q.v.).
70              
71             =head1 ATTRIBUTES
72              
73             =head2 outer
74              
75             The fallback C for looking up names not found in this C.
76             If non is provided, it is C, and no fallback will happen.
77              
78             =head2 local
79              
80             (Default falsy.) If truthy, do not go past this scope when doing local
81             lookups (see L below).
82              
83             =head2 name
84              
85             Not used, but provided so you can use L to make Scopes.
86              
87             =head2 merge_strategy
88              
89             How the inputs of L will be treated. Case-insensitive. Note that
90             changes after the first time you call L will be ignored!
91             (TODO change this - just need a custom setter)
92              
93             Values are:
94              
95             =over
96              
97             =item C or C<'combine'> (default)
98              
99             L. Same-name keys
100             are merged, so no data is lost.
101              
102             =item C<'keep'>
103              
104             L. Existing data will not be replaced by
105             new data.
106              
107             =item C<'replace'>
108              
109             L. New data will replace existing data.
110             under a particular key will win.
111              
112             =back
113              
114             =head1 PARAMETERS
115              
116             The methods generally receive the same parameters. They are as follows.
117              
118             =head2 $name
119              
120             The name of an item to be looked up. Names must be truthy. That means,
121             among other things, that C<'0'> is not a valid name.
122              
123             =head2 $set
124              
125             A Scope can have multiple sets of data. C<$set> specifies which one to
126             look in.
127              
128             =over
129              
130             =item *
131              
132             If specified as a number or a name, look only in that set.
133              
134             =item *
135              
136             If C<'*'>, look in every available set at this level, and return a
137             hashref of C<< { set_name => value } >>.
138             Note that this is not recursive --- it won't collect all instances
139             of the given name from all sets in all the levels. (TODO? change this?)
140              
141             =item *
142              
143             If L, look in only the first set (usually named C<0>).
144              
145             =item *
146              
147             If unspecified or undefined, look in every available set at this level, and
148             return the first one found, regardless of which set it comes from.
149              
150             =back
151              
152             =head2 $levels
153              
154             How many levels up (L) to go when performing an operation. Note:
155             chains more than C (L) Scopes long may fail in
156             unexpected ways, depending on your platform! For 32- or 64-bit platforms,
157             that number is at least 2,000,000,000, so you're probably OK :) .
158              
159             =over
160              
161             =item *
162              
163             If numeric and non-negative, go up that many more levels
164             (i.e., C<$levels==0> means only return this scope's local names).
165              
166             =item *
167              
168             If C<'local'>, go up until reaching a scope with L set.
169             If the current scope has L set, don't go up at all.
170              
171             =item *
172              
173             If not provided or not defined, go all the way to the outermost Scope.
174              
175             =back
176              
177             =head1 METHODS
178              
179             See also L, below, which is part of the public API.
180              
181             =cut
182              
183             # }}}1
184              
185             # Handle $levels and invoke a function on the outer scope if appropriate.
186             # Usage:
187             # $self->_invoke('method_name', $levels, [other args to be passed, starting
188             # with invocant, if any]
189             # A new levels value will be added to the end of the args as -levels=>$val.
190             # Returns undef if there's no more traversing to be done.
191              
192             sub _invoke {
193 731 50   731   1840 my $self = shift or croak 'Need an instance';
194 731 50       1485 my $method_name = shift or croak 'Need a method name';
195 731         1071 my $levels = shift;
196              
197             # Handle 'local'-scoped searches by terminating when $self->local is set.
198 731 100 100     4740 $levels = 0 if ( ($levels//'') eq _LOCAL) && $self->local;
      100        
199              
200             # Search the outer scopes
201 731 100 100     14577 if($self->outer && # Search the outer scopes
      100        
202             (!defined($levels) || ($levels eq _LOCAL) || ($levels>0) )
203             ) {
204 544 100       5042 my $newlevels =
    100          
205             !defined($levels) ? undef :
206             ( ($levels eq _LOCAL) ? _LOCAL : ($levels-1) );
207              
208 544         8817 unshift @_, $self->outer;
209 544         2914 push @_, -levels => $newlevels;
210 544         8467 my $coderef = $self->outer->can($method_name);
211 544 50       4229 return $coderef->(@_) if $coderef;
212             }
213 187         1768 return undef;
214             } #_invoke()
215              
216             =head2 find
217              
218             Find a named data item in the scope and return it. Looks up the scope chain
219             to the outermost scope if necessary. Returns undef on
220             failure. Usage:
221              
222             $scope->find($name[, $set[, $levels]]);
223             $scope->find($name[, -set => $set][, -levels => $levels]);
224             # Alternative using named arguments
225              
226             Dies if given a falsy name, notably, C<'0'>.
227              
228             =cut
229              
230             sub find {
231 544     544 1 8357 my ($self, %args) = getparameters('self', [qw(name ; set levels)], @_);
232 544 50       41428 croak 'Need a name' unless $args{name};
233             # Therefore, '0' is not a valid name
234 544         964 my $levels = $args{levels};
235              
236 544         2052 my $here = $self->_find_here($args{name}, $args{set});
237 544 100       3248 return $here if defined $here;
238              
239             return $self->_invoke('find', $args{levels},
240 207         878 forward_opts(\%args, {'-'=>1}, qw(name set))
241             );
242             } #find()
243              
244             =head2 names
245              
246             Returns a L of the names of the items available through this
247             Scope, optionally including all its parent Scopes (if any). Usage
248             and example:
249              
250             my $set = $scope->names([$levels]);
251             say "Name $_ is available" foreach @$set; # Set::Scalar supports @$set
252              
253             If no names are available in the given C<$levels>, returns an empty
254             C.
255              
256             TODO support a C<$set> parameter
257              
258             =cut
259              
260             sub names {
261 74     74 1 11231 my ($self, %args) = getparameters('self', [qw(; levels)], @_);
262 74         4483 my $retval = Set::Scalar->new;
263 74         6677 $self->_fill_names($retval, $args{levels});
264 74         407 return $retval;
265             } #names()
266              
267             # Implementation of names()
268             sub _fill_names {
269             #say Dumper(\@_);
270 191     191   807 my ($self, %args) = getparameters('self', [qw(retval levels)], @_);
271              
272 191         21058 $self->_names_here($args{retval}); # Insert this scope's names
273              
274 191         11486 return $self->_invoke('_fill_names', $args{levels}, -retval=>$args{retval});
275             } #_fill_names()
276              
277             =head2 as_hashref
278              
279             Returns a hash of the items available through this Scope, optionally
280             including all its parent Scopes (if any). Usage:
281              
282             my $hashref = $scope->as_hashref([-levels => $levels][, -deep => $deep])
283              
284             If C<$levels> is provided and nonzero, go up that many more levels
285             (i.e., C<$levels==0> means only return this scope's local names).
286             If C<$levels> is not provided, go all the way to the outermost Scope.
287              
288             If C<$deep> is provided and truthy, make a deep copy of each value (using
289             L. Otherwise, just copy.
290              
291             TODO support a C<$set> parameter
292              
293             =cut
294              
295             sub as_hashref {
296 107     107 1 1627 my ($self, %args) = getparameters('self', [qw(; levels deep)], @_);
297 107         4762 my $hrRetval = {};
298 107         523 $self->_fill_hashref($hrRetval, $args{deep}, $args{levels});
299 107         13695 return $hrRetval;
300             } #as_hashref()
301              
302             # Implementation of as_hashref. Mutates the provided $hrRetval.
303             sub _fill_hashref {
304 333     333   1421 my ($self, %args) = getparameters('self', [qw(retval levels deep)], @_);
305 333         31700 my $hrRetval = $args{retval};
306              
307             # Innermost wins, so copy ours first.
308 333         1292 my $names = Set::Scalar->new;
309 333         21568 $self->_names_here($names);
310              
311 333         20080 foreach my $k (@$names) {
312 281 100       1925 unless(exists($hrRetval->{$k})) { # An inner scope might have set it
313 199         547 my $val = $self->find($k, -levels => 0);
314             $hrRetval->{$k} =
315 199 100       716 ($args{deep} ? clone($val) : $val);
316             }
317             }
318              
319             return $self->_invoke('_fill_hashref', $args{levels},
320 333         3041 forward_opts(\%args, {'-'=>1}, qw(retval deep)));
321             } #_fill_hashref()
322              
323             =head2 outerize
324              
325             Set L, and return a scalar that will restore L when it
326             goes out of scope. Usage:
327              
328             my $saver = $scope->outerize($new_outer);
329              
330             C<$new_outer> may be C or a valid C.
331              
332             =cut
333              
334             sub outerize {
335 113     113 1 2332 my ($self, %args) = getparameters('self', [qw(outer)], @_);
336              
337             croak 'Need a Scope' unless
338             (!defined($args{outer})) or
339 113 50 33     6252 (ref $args{outer} && eval { $args{outer}->DOES('Data::Hopen::Scope') });
  103   66     914  
340              
341             # Protect the author of this function from himself
342 113 50       326 croak 'Sorry, but I must insist that you save my return value'
343             unless defined wantarray;
344              
345 113         2418 my $old_outer = $self->outer;
346 113     113   1134 my $saver = scope_finalizer { $self->outer($old_outer) };
  113         6245  
347 113         4051 $self->outer($args{outer});
348 113         691 return $saver;
349             } #outerize()
350              
351             =head2 _merger (internal)
352              
353             Creates a L instance based on L, if one
354             doesn't exist. Returns the instance.
355              
356             Provided for the convenience of subclasses; not actually used by
357             any concrete functions in this package.
358              
359             =cut
360              
361             sub _merger {
362 39 50   39   117 my $self = shift or croak 'Need an instance';
363 39 100       947 return $self->_merger_instance if $self->_merger_instance;
364              
365 28         634 my $s = $self->merge_strategy;
366 28 50       328 my $precedence =
    100          
    100          
    100          
367             !defined $s ? 'RETAINMENT_PRECEDENT' :
368             $s =~ /^combine$/i ? 'RETAINMENT_PRECEDENT' :
369             $s =~ /^keep$/i ? 'LEFT_PRECEDENT' :
370             $s =~ /^replace$/i ? 'RIGHT_PRECEDENT' :
371             undef;
372 28 50       90 die "Invalid merge strategy $s" unless defined $precedence;
373              
374 28         179 my $merger = Hash::Merge->new($precedence);
375 28         4023 $merger->set_clone_behavior(false);
376             # TODO CHECKME --- I would rather clone everything except blessed
377             # references, but doing so appears to be nontrivial. For now,
378             # I am trying not cloning.
379 28         1043 $self->_merger_instance($merger);
380              
381 28         239 return $merger;
382             } #_merger()
383              
384             =head1 FUNCTIONS TO BE OVERRIDDEN IN SUBCLASSES
385              
386             To implement a Scope with a different data-storage model than the hash
387             this class uses, subclass Scope and override these functions. Of these,
388             only L and L are part of the public API.
389              
390             =head2 put
391              
392             Add key-value pairs to this scope. Returns the scope so you can
393             chain. Example usage:
394              
395             my $scope = Data::Hopen::Scope->new()->put(foo => 1);
396              
397             C overwrites data in case of any conflicts. See L if you
398             want more control.
399              
400             C may be called with no parameters, in which case it is a no-op.
401             This is so you can say C<< $s->put(%foo) >> without first having to
402             check whether C<%foo> is nonempty.
403              
404             TODO add C<$set> option. TODO? add -deep option?
405              
406             =cut
407              
408             sub put {
409             ...
410 0     0 1 0 } #put()
411              
412             =head2 merge
413              
414             Merges key-value pairs into this scope. Returns the scope so you can
415             chain. Example usage:
416              
417             my $scope = Data::Hopen::Scope->new()->merge(foo => 1);
418              
419             See L for options controlling the behaviour of C.
420             =cut
421              
422             sub merge { #blub blub
423 0 0   0 1 0 my $self = shift or croak 'Need an instance';
424 0         0 my $merger = $self->_merger;
425             ...
426 0         0 } #merge()
427              
428             =head2 _names_here
429              
430             Populates a L with the names of the items stored in this Scope,
431             but B any outer Scope. Called as:
432              
433             $scope->_names_here($retval[, $set])
434              
435             C<$retval> is the C instance. C<$set> is as
436             defined L.
437              
438             No return value.
439              
440             =cut
441              
442             sub _names_here {
443             ...
444 0     0   0 } #_names_here()
445              
446             =head2 _find_here
447              
448             Looks for a given item in this scope, but B any outer scope. Called as:
449              
450             $scope->_find_here($name[, $set])
451              
452             Returns the value, or C if not found.
453              
454             =cut
455              
456             sub _find_here {
457             ...
458 0     0   0 } #_find_here()
459              
460             =head1 HELPER FUNCTIONS
461              
462             =head2 is_first_only
463              
464             Test whether the given scalar is L. Usage: C.
465              
466             =cut
467              
468             sub is_first_only {
469 1078 100 100 1078 1 3675 ref $_[0] &&
470             ref $_[0] eq ref $_first_only &&
471             refaddr $_[0] == refaddr $_first_only
472             } #is_first_only()
473              
474             =head2 _set0
475              
476             For use only by subclasses.
477              
478             Don't support C<-set>, but permit C<< -set=>0 >> and C<< -set=>FIRST_ONLY >>
479             for the sake of code calling through the Scope interface. Call as
480             C>. Returns truthy if OK, falsy if not. May modify its argument.
481             Better a readily-obvious crash than a subtle bug!
482              
483             =cut
484              
485             sub _set0 {
486 1073   100 1073   6030 $_[0] //= 0; # Give the caller a default set
487 1073 100       2172 $_[0] = 0 if Data::Hopen::Scope::is_first_only($_[0]);
488 1073         2038 my $set = shift;
489 1073 100 100     2654 return false if $set ne '0' && $set ne '*';
490 1071         2892 return true;
491             } #_set0()
492              
493             1;
494             __END__