————————# Build::Hopen::Scope - a nested key-value store
package
Build::Hopen::Scope;
use
Build::Hopen::Base;
our
$VERSION
=
'0.000006'
;
# TRIAL
# Class definition
use
Class::Tiny {
outer
=>
undef
,
local
=> false,
name
=>
'anonymous scope'
,
# Internal
_first_set
=>
undef
,
# name of the first set
};
# Static exports
our
@EXPORT
; BEGIN {
@EXPORT
=
qw(FIRST_ONLY)
; }
my
$_first_only
= {};
sub
FIRST_ONLY {
$_first_only
}
# What we use
use
Config;
use
Build::Hopen::Arrrgs;
use
POSIX ();
use
Set::Scalar;
# Docs {{{1
=head1 NAME
Build::Hopen::Scope - a nested key-value store.
=head1 SYNOPSIS
A Scope represents a set of data available to operations. It is a
key-value store that falls back to an outer C<Scope> if a requested key
isn't found.
This class is the abstract base of Scopes. See L<Build::Hopen::Scope::Hash>
for an example of a concrete implementation using a hash under the
hood. Different subclasses use different representations.
See L</"FUNCTIONS TO BE OVERRIDDEN IN SUBCLASSES"> for more on that topic.
=head1 STATIC EXPORTS
=head2 FIRST_ONLY
A flag used as a L</$set> (q.v.).
=head1 ATTRIBUTES
=head2 outer
The fallback C<Scope> for looking up names not found in this C<Scope>.
If non is provided, it is C<undef>, and no fallback will happen.
=head2 local
(Default falsy.) If truthy, do not go past this scope when doing local
lookups (see L</$levels> below).
=head2 name
Not used, but provided so you can use L<Build::Hopen/hnew> to make Scopes.
=head1 PARAMETERS
The methods generally receive the same parameters. They are as follows.
=head2 $name
The name of an item to be looked up. Names must be truthy. That means,
among other things, that C<'0'> is not a valid key.
=head2 $set
A Scope can have multiple sets of data. C<$set> specifies which one to
look in.
=over
=item *
If specified as a number or a name, look only in that set.
=item *
If C<'*'>, look in every available set at this level, and return a
hashref of C<< { set_name => value } >>.
Note that this is not recursive --- it won't collect all instances
of the given name from all sets in all the levels. (TODO? change this?)
=item *
If L</FIRST_ONLY>, look in only the first set (usually named C<0>).
=item *
If unspecified or undefined, look in every available set at this level, and
return the first one found, regardless of which set it comes from.
=back
=head2 $levels
How many levels up (L</outer>) to go when performing an operation. Note:
chains more than C<POSIX::INT_MAX> (L<POSIX/LIMITS>) Scopes long may fail in
unexpected ways, depending on your platform! For 32- or 64-bit platforms,
that number is at least 2,000,000,000, so you're probably OK :) .
=over
=item *
If numeric and non-negative, go up that many more levels
(i.e., C<$levels==0> means only return this scope's local names).
=item *
If C<'local'>, go up until reaching a scope with L</local> set.
If the current scope has L</local> set, don't go up at all.
=item *
If not provided or not defined, go all the way to the outermost Scope.
=back
=head1 METHODS
See also L</add>, below, which is part of the public API.
=cut
# }}}1
# Handle $levels and invoke a function on the outer scope if appropriate.
# Usage:
# $self->_invoke(coderef, $levels, [other args to be passed, starting with
# invocant, if any]
# A new levels value will be added to the end of the args as -levels=>$val.
# Returns undef if there's no more traversing to be done.
sub
_invoke {
my
$self
=
shift
or croak
'Need an instance'
;
my
$coderef
=
shift
or croak
'Need a coderef'
;
my
$levels
=
shift
;
# Handle 'local'-scoped searches by terminating when $self->local is set.
$levels
= 0
if
( (
$levels
//
''
) eq _LOCAL) &&
$self
->
local
;
# Search the outer scopes
if
(
$self
->outer &&
# Search the outer scopes
(!
defined
(
$levels
) || (
$levels
eq _LOCAL) || (
$levels
>0) )
) {
my
$newlevels
=
!
defined
(
$levels
) ?
undef
:
( (
$levels
eq _LOCAL) ? _LOCAL : (
$levels
-1) );
unshift
@_
,
$self
->outer;
push
@_
,
-levels
=>
$newlevels
;
goto
&$coderef
;
}
return
undef
;
}
#_invoke()
=head2 find
Find a named data item in the scope and return it. Looks up the scope chain
to the outermost scope if necessary. Returns undef on
failure. Usage:
$scope->find($name[, $set[, $levels]]);
$scope->find($name[, -set => $set][, -levels => $levels]);
# Alternative using named arguments
Dies if given a falsy name, notably, C<'0'>.
=cut
sub
find {
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(name ; set levels)
],
@_
);
croak
'Need a name'
unless
$args
{name};
# Therefore, '0' is not a valid name
my
$levels
=
$args
{levels};
my
$here
=
$self
->_find_here(
$args
{name},
$args
{set});
return
$here
if
defined
$here
;
return
$self
->_invoke(\
&find
,
$args
{levels},
forward_opts(\
%args
, {
'-'
=>1},
qw(name set)
)
);
}
#find()
=head2 names
Returns a L<Set::Scalar> of the names of the items available through this
Scope, optionally including all its parent Scopes (if any). Usage
and example:
my $set = $scope->names([$levels]);
say "Name $_ is available" foreach @$set; # Set::Scalar supports @$set
TODO? Support a C<$set> parameter?
=cut
sub
names {
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(; levels)
],
@_
);
my
$retval
= Set::Scalar->new;
$self
->_fill_names(
$retval
,
$args
{levels});
return
$retval
;
}
#names()
# Implementation of names()
sub
_fill_names {
#say Dumper(\@_);
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(retval levels)
],
@_
);
$self
->_names_here(
$args
{retval});
# Insert this scope's names
return
$self
->_invoke(\
&_fill_names
,
$args
{levels},
-retval
=>
$args
{retval});
}
#_fill_names()
=head2 as_hashref
Returns a hash of the items available through this Scope, optionally
including all its parent Scopes (if any). Usage:
my $hashref = $scope->as_hashref([-levels => $levels][, -deep => $deep])
If C<$levels> is provided and nonzero, go up that many more levels
(i.e., C<$levels==0> means only return this scope's local names).
If C<$levels> is not provided, go all the way to the outermost Scope.
If C<$deep> is provided and truthy, make a deep copy of each value (using
L<Build::Hopen/clone>. Otherwise, just copy.
TODO? Support a C<$set> parameter?
=cut
sub
as_hashref {
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(; levels deep)
],
@_
);
my
$hrRetval
= {};
$self
->_fill_hashref(
$hrRetval
,
$args
{deep},
$args
{levels});
return
$hrRetval
;
}
#as_hashref()
# Implementation of as_hashref. Mutates the provided $hrRetval.
# TODO move this to subclasses.
sub
_fill_hashref {
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(retval levels deep)
],
@_
);
my
$hrRetval
=
$args
{retval};
# Innermost wins, so copy ours first.
foreach
my
$k
(
keys
%{
$self
->_content}) {
unless
(
exists
(
$hrRetval
->{
$k
})) {
# An inner scope might have set it
$hrRetval
->{
$k
} =
(
$args
{deep} ? clone(
$self
->_content->{
$k
}) :
$self
->_content->{
$k
});
}
}
return
$self
->_invoke(\
&_fill_hashref
,
$args
{levels},
forward_opts(\
%args
, {
'-'
=>1},
qw(retval deep)
));
}
#_fill_hashref()
=head2 outerize
Set L</outer>, and return a scalar that will restore L</outer> when it
goes out of scope. Usage:
my $saver = $scope->outerize($new_outer);
C<$new_outer> may be C<undef> or a valid C<Scope>.
=cut
sub
outerize {
my
(
$self
,
%args
) = parameters(
'self'
, [
qw(outer)
],
@_
);
croak
'Need a Scope'
unless
(!
defined
(
$args
{outer})) or
(
ref
$args
{outer} &&
eval
{
$args
{outer}->DOES(
'Build::Hopen::Scope'
) });
# Protect the author of this function from himself
croak
'Sorry, but I must insist that you save my return value'
unless
defined
wantarray
;
my
$old_outer
=
$self
->outer;
my
$saver
= scope_finalizer {
$self
->outer(
$old_outer
) };
$self
->outer(
$args
{outer});
return
$saver
;
}
#outerize()
=head1 FUNCTIONS TO BE OVERRIDDEN IN SUBCLASSES
To implement a Scope with a different data-storage model than the hash
this class uses, subclass Scope and override these functions. Only L</add>
is part of the public API.
=head2 add
Add key-value pairs to this scope. Returns the scope so you can
chain. Example usage:
my $scope = Build::Hopen::Scope->new()->add(foo => 1);
C<add> is responsible for handling any conflicts that may occur. In this
particular implementation, the last-added value for a particular key wins.
TODO add $set option.
=cut
sub
add {
...
}
#add()
=head2 _names_here
Populates a L<Set::Scalar> with the names of the items stored in this Scope,
but B<not> any outer Scope. Called as:
$scope->_names_here($retval[, $set])
C<$retval> is the C<Set::Scalar> instance. C<$set> is as
defined L<above|/$set>.
No return value.
=cut
sub
_names_here {
...
}
#_names_here()
=head2 _find_here
Looks for a given item in this scope, but B<not> any outer scope. Called as:
$scope->_find_here($name[, $set])
Returns the value, or C<undef> if not found.
=cut
sub
_find_here {
...
}
#_find_here()
1;
__END__
# vi: set fdm=marker: #