File Coverage

blib/lib/Build/Hopen/Scope.pm
Criterion Covered Total %
statement 75 79 94.9
branch 18 24 75.0
condition 13 17 76.4
subroutine 19 23 82.6
pod 6 6 100.0
total 131 149 87.9


line stmt bran cond sub pod time code
1             # Build::Hopen::Scope - a nested key-value store
2             package Build::Hopen::Scope;
3 10     10   4735 use Build::Hopen::Base;
  10         22  
  10         61  
4 10     10   2249 use Exporter 'import';
  10         19  
  10         596  
5              
6             our $VERSION = '0.000008'; # TRIAL
7              
8             # Class definition
9             use Class::Tiny {
10 10         74 outer => undef,
11             local => false,
12             name => 'anonymous scope',
13              
14             # Internal
15             _first_set => undef, # name of the first set
16 10     10   5137 };
  10         18635  
17              
18             # Static exports
19 10     10   7047 our @EXPORT; BEGIN { @EXPORT=qw(FIRST_ONLY); }
20              
21             my $_first_only = {};
22 0     0 1 0 sub FIRST_ONLY { $_first_only }
23              
24 10     10   78 use constant _LOCAL => 'local';
  10         19  
  10         605  
25              
26             # What we use
27 10     10   62 use Config;
  10         21  
  10         569  
28 10     10   4707 use Build::Hopen::Arrrgs;
  10         27  
  10         535  
29 10     10   5150 use POSIX ();
  10         65349  
  10         337  
30 10     10   4520 use Build::Hopen::Util::Data qw(clone forward_opts);
  10         26  
  10         650  
31 10     10   4157 use Set::Scalar;
  10         97484  
  10         558  
32 10     10   4600 use Sub::ScopeFinalizer qw(scope_finalizer);
  10         5255  
  10         10643  
33              
34             # Docs {{{1
35              
36             =head1 NAME
37              
38             Build::Hopen::Scope - a nested key-value store.
39              
40             =head1 SYNOPSIS
41              
42             A Scope represents a set of data available to operations. It is a
43             key-value store that falls back to an outer C if a requested key
44             isn't found.
45              
46             This class is the abstract base of Scopes. See L
47             for an example of a concrete implementation using a hash under the
48             hood. Different subclasses use different representations.
49             See L for more on that topic.
50              
51             =head1 STATIC EXPORTS
52              
53             =head2 FIRST_ONLY
54              
55             A flag used as a L (q.v.).
56              
57             =head1 ATTRIBUTES
58              
59             =head2 outer
60              
61             The fallback C for looking up names not found in this C.
62             If non is provided, it is C, and no fallback will happen.
63              
64             =head2 local
65              
66             (Default falsy.) If truthy, do not go past this scope when doing local
67             lookups (see L below).
68              
69             =head2 name
70              
71             Not used, but provided so you can use L to make Scopes.
72              
73             =head1 PARAMETERS
74              
75             The methods generally receive the same parameters. They are as follows.
76              
77             =head2 $name
78              
79             The name of an item to be looked up. Names must be truthy. That means,
80             among other things, that C<'0'> is not a valid key.
81              
82             =head2 $set
83              
84             A Scope can have multiple sets of data. C<$set> specifies which one to
85             look in.
86              
87             =over
88              
89             =item *
90              
91             If specified as a number or a name, look only in that set.
92              
93             =item *
94              
95             If C<'*'>, look in every available set at this level, and return a
96             hashref of C<< { set_name => value } >>.
97             Note that this is not recursive --- it won't collect all instances
98             of the given name from all sets in all the levels. (TODO? change this?)
99              
100             =item *
101              
102             If L, look in only the first set (usually named C<0>).
103              
104             =item *
105              
106             If unspecified or undefined, look in every available set at this level, and
107             return the first one found, regardless of which set it comes from.
108              
109             =back
110              
111             =head2 $levels
112              
113             How many levels up (L) to go when performing an operation. Note:
114             chains more than C (L) Scopes long may fail in
115             unexpected ways, depending on your platform! For 32- or 64-bit platforms,
116             that number is at least 2,000,000,000, so you're probably OK :) .
117              
118             =over
119              
120             =item *
121              
122             If numeric and non-negative, go up that many more levels
123             (i.e., C<$levels==0> means only return this scope's local names).
124              
125             =item *
126              
127             If C<'local'>, go up until reaching a scope with L set.
128             If the current scope has L set, don't go up at all.
129              
130             =item *
131              
132             If not provided or not defined, go all the way to the outermost Scope.
133              
134             =back
135              
136             =head1 METHODS
137              
138             See also L, below, which is part of the public API.
139              
140             =cut
141              
142             # }}}1
143              
144             # Handle $levels and invoke a function on the outer scope if appropriate.
145             # Usage:
146             # $self->_invoke(coderef, $levels, [other args to be passed, starting with
147             # invocant, if any]
148             # A new levels value will be added to the end of the args as -levels=>$val.
149             # Returns undef if there's no more traversing to be done.
150              
151             sub _invoke {
152 104 50   104   261 my $self = shift or croak 'Need an instance';
153 104 50       212 my $coderef = shift or croak 'Need a coderef';
154 104         153 my $levels = shift;
155              
156             # Handle 'local'-scoped searches by terminating when $self->local is set.
157 104 100 100     771 $levels = 0 if ( ($levels//'') eq _LOCAL) && $self->local;
      100        
158              
159             # Search the outer scopes
160 104 100 100     2121 if($self->outer && # Search the outer scopes
      100        
161             (!defined($levels) || ($levels eq _LOCAL) || ($levels>0) )
162             ) {
163 71 100       725 my $newlevels =
    100          
164             !defined($levels) ? undef :
165             ( ($levels eq _LOCAL) ? _LOCAL : ($levels-1) );
166              
167 71         1112 unshift @_, $self->outer;
168 71         488 push @_, -levels => $newlevels;
169 71         268 goto &$coderef;
170             }
171 33         337 return undef;
172             } #_invoke()
173              
174             =head2 find
175              
176             Find a named data item in the scope and return it. Looks up the scope chain
177             to the outermost scope if necessary. Returns undef on
178             failure. Usage:
179              
180             $scope->find($name[, $set[, $levels]]);
181             $scope->find($name[, -set => $set][, -levels => $levels]);
182             # Alternative using named arguments
183              
184             Dies if given a falsy name, notably, C<'0'>.
185              
186             =cut
187              
188             sub find {
189 86     86 1 6023 my ($self, %args) = parameters('self', [qw(name ; set levels)], @_);
190 86 50       252 croak 'Need a name' unless $args{name};
191             # Therefore, '0' is not a valid name
192 86         145 my $levels = $args{levels};
193              
194 86         170 $DB::single=1;
195 86         334 my $here = $self->_find_here($args{name}, $args{set});
196 86 100       606 return $here if defined $here;
197              
198             return $self->_invoke(\&find, $args{levels},
199 47         218 forward_opts(\%args, {'-'=>1}, qw(name set))
200             );
201             } #find()
202              
203             =head2 names
204              
205             Returns a L of the names of the items available through this
206             Scope, optionally including all its parent Scopes (if any). Usage
207             and example:
208              
209             my $set = $scope->names([$levels]);
210             say "Name $_ is available" foreach @$set; # Set::Scalar supports @$set
211              
212             If no names are available in the given C<$levels>, returns an empty
213             C.
214              
215             TODO? Support a C<$set> parameter?
216              
217             =cut
218              
219             sub names {
220 26     26 1 10834 my ($self, %args) = parameters('self', [qw(; levels)], @_);
221 26         161 my $retval = Set::Scalar->new;
222 26         2448 $self->_fill_names($retval, $args{levels});
223 26         208 return $retval;
224             } #names()
225              
226             # Implementation of names()
227             sub _fill_names {
228             #say Dumper(\@_);
229 54     54   182 my ($self, %args) = parameters('self', [qw(retval levels)], @_);
230              
231 54         256 $self->_names_here($args{retval}); # Insert this scope's names
232              
233 54         3119 return $self->_invoke(\&_fill_names, $args{levels}, -retval=>$args{retval});
234             } #_fill_names()
235              
236             =head2 as_hashref
237              
238             Returns a hash of the items available through this Scope, optionally
239             including all its parent Scopes (if any). Usage:
240              
241             my $hashref = $scope->as_hashref([-levels => $levels][, -deep => $deep])
242              
243             If C<$levels> is provided and nonzero, go up that many more levels
244             (i.e., C<$levels==0> means only return this scope's local names).
245             If C<$levels> is not provided, go all the way to the outermost Scope.
246              
247             If C<$deep> is provided and truthy, make a deep copy of each value (using
248             L. Otherwise, just copy.
249              
250             TODO? Support a C<$set> parameter?
251              
252             =cut
253              
254             sub as_hashref {
255 1     1 1 5 my ($self, %args) = parameters('self', [qw(; levels deep)], @_);
256 1         3 my $hrRetval = {};
257 1         18 $self->_fill_hashref($hrRetval, $args{deep}, $args{levels});
258 1         7 return $hrRetval;
259             } #as_hashref()
260              
261             # Implementation of as_hashref. Mutates the provided $hrRetval.
262             # TODO move this to subclasses.
263             sub _fill_hashref {
264 3     3   14 my ($self, %args) = parameters('self', [qw(retval levels deep)], @_);
265 3         7 my $hrRetval = $args{retval};
266              
267             # Innermost wins, so copy ours first.
268 3         6 foreach my $k (keys %{$self->_content}) {
  3         62  
269 2 100       17 unless(exists($hrRetval->{$k})) { # An inner scope might have set it
270             $hrRetval->{$k} =
271 1 50       27 ($args{deep} ? clone($self->_content->{$k}) : $self->_content->{$k});
272             }
273             }
274              
275             return $self->_invoke(\&_fill_hashref, $args{levels},
276 3         18 forward_opts(\%args, {'-'=>1}, qw(retval deep)));
277             } #_fill_hashref()
278              
279             =head2 outerize
280              
281             Set L, and return a scalar that will restore L when it
282             goes out of scope. Usage:
283              
284             my $saver = $scope->outerize($new_outer);
285              
286             C<$new_outer> may be C or a valid C.
287              
288             =cut
289              
290             sub outerize {
291 9     9 1 166 my ($self, %args) = parameters('self', [qw(outer)], @_);
292              
293             croak 'Need a Scope' unless
294             (!defined($args{outer})) or
295 9 50 33     48 (ref $args{outer} && eval { $args{outer}->DOES('Build::Hopen::Scope') });
  9   33     81  
296              
297             # Protect the author of this function from himself
298 9 50       26 croak 'Sorry, but I must insist that you save my return value'
299             unless defined wantarray;
300              
301 9         190 my $old_outer = $self->outer;
302 9     9   84 my $saver = scope_finalizer { $self->outer($old_outer) };
  9         294  
303 9         321 $self->outer($args{outer});
304 9         58 return $saver;
305             } #outerize()
306              
307             =head1 FUNCTIONS TO BE OVERRIDDEN IN SUBCLASSES
308              
309             To implement a Scope with a different data-storage model than the hash
310             this class uses, subclass Scope and override these functions. Only L
311             is part of the public API.
312              
313             =head2 add
314              
315             Add key-value pairs to this scope. Returns the scope so you can
316             chain. Example usage:
317              
318             my $scope = Build::Hopen::Scope->new()->add(foo => 1);
319              
320             C is responsible for handling any conflicts that may occur. In this
321             particular implementation, the last-added value for a particular key wins.
322              
323             TODO add C<$set> option. TODO? add -deep option?
324              
325             =cut
326              
327             sub add {
328             ...
329 0     0 1   } #add()
330              
331             =head2 _names_here
332              
333             Populates a L with the names of the items stored in this Scope,
334             but B any outer Scope. Called as:
335              
336             $scope->_names_here($retval[, $set])
337              
338             C<$retval> is the C instance. C<$set> is as
339             defined L.
340              
341             No return value.
342              
343             =cut
344              
345             sub _names_here {
346             ...
347 0     0     } #_names_here()
348              
349             =head2 _find_here
350              
351             Looks for a given item in this scope, but B any outer scope. Called as:
352              
353             $scope->_find_here($name[, $set])
354              
355             Returns the value, or C if not found.
356              
357             =cut
358              
359             sub _find_here {
360             ...
361 0     0     } #_find_here()
362              
363             1;
364             __END__