File Coverage

blib/lib/Build/Hopen/Scope.pm
Criterion Covered Total %
statement 63 78 80.7
branch 14 24 58.3
condition 12 17 70.5
subroutine 17 23 73.9
pod 6 6 100.0
total 112 148 75.6


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   4007 use Build::Hopen::Base;
  10         18  
  10         47  
4 10     10   1710 use Exporter 'import';
  10         31  
  10         568  
5              
6             our $VERSION = '0.000006'; # TRIAL
7              
8             # Class definition
9             use Class::Tiny {
10 10         62 outer => undef,
11             local => false,
12             name => 'anonymous scope',
13              
14             # Internal
15             _first_set => undef, # name of the first set
16 10     10   3979 };
  10         14843  
17              
18             # Static exports
19 10     10   5556 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   55 use constant _LOCAL => 'local';
  10         18  
  10         553  
25              
26             # What we use
27 10     10   54 use Config;
  10         17  
  10         417  
28 10     10   3844 use Build::Hopen::Arrrgs;
  10         21  
  10         426  
29 10     10   4103 use POSIX ();
  10         52226  
  10         284  
30 10     10   3698 use Build::Hopen::Util::Data qw(forward_opts);
  10         26  
  10         517  
31 10     10   3830 use Set::Scalar;
  10         87774  
  10         487  
32 10     10   3736 use Sub::ScopeFinalizer qw(scope_finalizer);
  10         4236  
  10         8536  
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 64 50   64   149 my $self = shift or croak 'Need an instance';
153 64 50       123 my $coderef = shift or croak 'Need a coderef';
154 64         83 my $levels = shift;
155              
156             # Handle 'local'-scoped searches by terminating when $self->local is set.
157 64 50 100     632 $levels = 0 if ( ($levels//'') eq _LOCAL) && $self->local;
      66        
158              
159             # Search the outer scopes
160 64 100 100     1149 if($self->outer && # Search the outer scopes
      100        
161             (!defined($levels) || ($levels eq _LOCAL) || ($levels>0) )
162             ) {
163 45 100       366 my $newlevels =
    100          
164             !defined($levels) ? undef :
165             ( ($levels eq _LOCAL) ? _LOCAL : ($levels-1) );
166              
167 45         573 unshift @_, $self->outer;
168 45         213 push @_, -levels => $newlevels;
169 45         159 goto &$coderef;
170             }
171 19         127 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 44     44 1 2950 my ($self, %args) = parameters('self', [qw(name ; set levels)], @_);
190 44 50       112 croak 'Need a name' unless $args{name};
191             # Therefore, '0' is not a valid name
192 44         65 my $levels = $args{levels};
193              
194 44         151 my $here = $self->_find_here($args{name}, $args{set});
195 44 100       329 return $here if defined $here;
196              
197             return $self->_invoke(\&find, $args{levels},
198 22         99 forward_opts(\%args, {'-'=>1}, qw(name set))
199             );
200             } #find()
201              
202             =head2 names
203              
204             Returns a L of the names of the items available through this
205             Scope, optionally including all its parent Scopes (if any). Usage
206             and example:
207              
208             my $set = $scope->names([$levels]);
209             say "Name $_ is available" foreach @$set; # Set::Scalar supports @$set
210              
211             TODO? Support a C<$set> parameter?
212              
213             =cut
214              
215             sub names {
216 18     18 1 5564 my ($self, %args) = parameters('self', [qw(; levels)], @_);
217 18         110 my $retval = Set::Scalar->new;
218 18         1511 $self->_fill_names($retval, $args{levels});
219 18         71 return $retval;
220             } #names()
221              
222             # Implementation of names()
223             sub _fill_names {
224             #say Dumper(\@_);
225 42     42   121 my ($self, %args) = parameters('self', [qw(retval levels)], @_);
226              
227 42         157 $self->_names_here($args{retval}); # Insert this scope's names
228              
229 42         1921 return $self->_invoke(\&_fill_names, $args{levels}, -retval=>$args{retval});
230             } #_fill_names()
231              
232             =head2 as_hashref
233              
234             Returns a hash of the items available through this Scope, optionally
235             including all its parent Scopes (if any). Usage:
236              
237             my $hashref = $scope->as_hashref([-levels => $levels][, -deep => $deep])
238              
239             If C<$levels> is provided and nonzero, go up that many more levels
240             (i.e., C<$levels==0> means only return this scope's local names).
241             If C<$levels> is not provided, go all the way to the outermost Scope.
242              
243             If C<$deep> is provided and truthy, make a deep copy of each value (using
244             L. Otherwise, just copy.
245              
246             TODO? Support a C<$set> parameter?
247              
248             =cut
249              
250             sub as_hashref {
251 0     0 1 0 my ($self, %args) = parameters('self', [qw(; levels deep)], @_);
252 0         0 my $hrRetval = {};
253 0         0 $self->_fill_hashref($hrRetval, $args{deep}, $args{levels});
254 0         0 return $hrRetval;
255             } #as_hashref()
256              
257             # Implementation of as_hashref. Mutates the provided $hrRetval.
258             # TODO move this to subclasses.
259             sub _fill_hashref {
260 0     0   0 my ($self, %args) = parameters('self', [qw(retval levels deep)], @_);
261 0         0 my $hrRetval = $args{retval};
262              
263             # Innermost wins, so copy ours first.
264 0         0 foreach my $k (keys %{$self->_content}) {
  0         0  
265 0 0       0 unless(exists($hrRetval->{$k})) { # An inner scope might have set it
266             $hrRetval->{$k} =
267 0 0       0 ($args{deep} ? clone($self->_content->{$k}) : $self->_content->{$k});
268             }
269             }
270              
271             return $self->_invoke(\&_fill_hashref, $args{levels},
272 0         0 forward_opts(\%args, {'-'=>1}, qw(retval deep)));
273             } #_fill_hashref()
274              
275             =head2 outerize
276              
277             Set L, and return a scalar that will restore L when it
278             goes out of scope. Usage:
279              
280             my $saver = $scope->outerize($new_outer);
281              
282             C<$new_outer> may be C or a valid C.
283              
284             =cut
285              
286             sub outerize {
287 7     7 1 137 my ($self, %args) = parameters('self', [qw(outer)], @_);
288              
289             croak 'Need a Scope' unless
290             (!defined($args{outer})) or
291 7 50 33     33 (ref $args{outer} && eval { $args{outer}->DOES('Build::Hopen::Scope') });
  7   33     58  
292              
293             # Protect the author of this function from himself
294 7 50       20 croak 'Sorry, but I must insist that you save my return value'
295             unless defined wantarray;
296              
297 7         124 my $old_outer = $self->outer;
298 7     7   79 my $saver = scope_finalizer { $self->outer($old_outer) };
  7         444  
299 7         224 $self->outer($args{outer});
300 7         33 return $saver;
301             } #outerize()
302              
303             =head1 FUNCTIONS TO BE OVERRIDDEN IN SUBCLASSES
304              
305             To implement a Scope with a different data-storage model than the hash
306             this class uses, subclass Scope and override these functions. Only L
307             is part of the public API.
308              
309             =head2 add
310              
311             Add key-value pairs to this scope. Returns the scope so you can
312             chain. Example usage:
313              
314             my $scope = Build::Hopen::Scope->new()->add(foo => 1);
315              
316             C is responsible for handling any conflicts that may occur. In this
317             particular implementation, the last-added value for a particular key wins.
318              
319             TODO add $set option.
320              
321             =cut
322              
323             sub add {
324             ...
325 0     0 1   } #add()
326              
327             =head2 _names_here
328              
329             Populates a L with the names of the items stored in this Scope,
330             but B any outer Scope. Called as:
331              
332             $scope->_names_here($retval[, $set])
333              
334             C<$retval> is the C instance. C<$set> is as
335             defined L.
336              
337             No return value.
338              
339             =cut
340              
341             sub _names_here {
342             ...
343 0     0     } #_names_here()
344              
345             =head2 _find_here
346              
347             Looks for a given item in this scope, but B any outer scope. Called as:
348              
349             $scope->_find_here($name[, $set])
350              
351             Returns the value, or C if not found.
352              
353             =cut
354              
355             sub _find_here {
356             ...
357 0     0     } #_find_here()
358              
359             1;
360             __END__