File Coverage

blib/lib/Data/Reach.pm
Criterion Covered Total %
statement 69 73 94.5
branch 41 52 78.8
condition 17 18 94.4
subroutine 11 11 100.0
pod 1 1 100.0
total 139 155 89.6


line stmt bran cond sub pod time code
1             package Data::Reach;
2 6     6   160047 use strict;
  6         12  
  6         231  
3 6     6   38 use warnings;
  6         11  
  6         249  
4 6     6   36 use Carp qw/carp croak/;
  6         9  
  6         454  
5 6     6   38 use Scalar::Util qw/blessed reftype/;
  6         10  
  6         628  
6 6     6   9402 use overload;
  6         7892  
  6         44  
7            
8             our $VERSION = '1.00';
9            
10             # main entry point
11             sub reach ($@) {
12 42     42 1 4689 my ($root, @path) = @_;
13            
14             # loop until either @path or the datastructure under $root is exhausted
15 42         57 while (1) {
16            
17             # exit conditions
18 137 100       481 return undef if !defined $root;
19 133 100       280 return $root if !@path;
20 106         116 my $path0 = shift @path;
21 106 100       164 return undef if !defined $path0;
22            
23             # otherwise, walk down one step into the datastructure and loop again
24 105 100       324 $root = blessed $root ? _step_down_obj($root, $path0)
25             : _step_down_raw($root, $path0);
26             }
27             }
28            
29             # get inner data within a raw datastructure
30             sub _step_down_raw {
31 94     94   101 my ($data, $key) = @_;
32            
33 94   100     211 my $reftype = reftype $data || '';
34            
35 94 100       158 if ($reftype eq 'HASH') {
    100          
36 61         138 return $data->{$key};
37             }
38             elsif ($reftype eq 'ARRAY') {
39 25 100       95 if ($key =~ /^-?\d+$/) {
40 24         52 return $data->[$key];
41             }
42             else {
43 1         150 croak "cannot reach index '$key' within an array";
44             }
45             }
46             else {
47 8 50       27 my $kind = $reftype ? "${reftype}REF"
    100          
48             : defined ref $data ? "SCALAR"
49             : "undef";
50 8 50       32 my $article = $kind =~ /^[aeiou]/i ? "an" : "a";
51 8         1024 croak "cannot reach '$key' within $article $kind";
52             }
53             }
54            
55            
56             # get inner data within an object
57             sub _step_down_obj {
58 18     18   25 my ($obj, $key) = @_;
59            
60             # pragmata that may modify our algorithm -- see L
61 18         134 my $hint_hash = (caller(1))[10];
62 18   100     87 my $use_overloads = $hint_hash->{'Data::Reach::use_overloads'} // 1; # default
63 18   100     62 my $peek_blessed = $hint_hash->{'Data::Reach::peek_blessed'} // 1; # default
64            
65             # choice 1 : call named method in object
66 18   100     302 my @call_method = split $;, $hint_hash->{'Data::Reach::call_method'} || '';
67             METH_NAME:
68 18         42 foreach my $meth_name (@call_method) {
69 20 100       132 my $meth =$obj->can($meth_name)
70             or next METH_NAME;
71 8         22 return $obj->$meth($key);
72             }
73            
74             # choice 2 : use overloaded methods -- active by default
75 10 100       18 if ($use_overloads) {
76 9 100 100     27 return $obj->[$key] if overload::Method($obj, '@{}')
77             && $key =~ /^-?\d+$/;
78 7 50       1548 return $obj->{$key} if overload::Method($obj, '%{}');
79             }
80            
81             # choice 3 : use the object's internal representation -- active by default
82 8 100       173 if ($peek_blessed) {
83 7         16 return _step_down_raw($obj, $key);
84             }
85             else {
86 1         87 croak "cannot reach '$key' within an object of class " . ref $obj;
87             }
88             }
89            
90            
91            
92             # the 'import' method does 2 things : a) export the 'reach' function,
93             # like the regular Exporter, but possibly with a change of name;
94             # b) implement optional changes to the algorithm, lexically scoped
95             # through the %^H hint hash (see L).
96            
97             my %seen_pkg; # remember which packages we already exported into
98            
99             sub import {
100 10     10   76 my $class = shift;
101 10         22 my $pkg = caller;
102 10         11 my $export_as;
103            
104             # cheap parsing of import parameters -- I wish I could implement that
105             # with given/when, but unfortunately those constructs were dropped in v5.18.
106 10         36 while (my $option = shift) {
107 9 100       40 if ($option eq 'reach') {
    100          
    50          
    0          
    0          
108 1         4 $export_as = 'reach';
109             }
110             elsif ($option eq 'as') {
111 3         4 $export_as = shift;
112 3 50       37 defined $export_as
113             or croak "use Data::Reach : no export name after 'as'";
114             }
115             elsif ($option eq 'call_method') {
116 5 50       16 my $methods = shift
117             or croak "use Data::Reach : no method name after 'call_method'";
118 5 100 100     36 $methods = join $;, @$methods if (ref $methods || '') eq 'ARRAY';
119 5         34 $^H{"Data::Reach::call_method"} = $methods;
120             }
121             elsif ($option eq 'peek_blessed') {
122 0         0 $^H{"Data::Reach::peek_blessed"} = 1;
123             }
124             elsif ($option eq 'use_overloads') {
125 0         0 $^H{"Data::Reach::use_overloads"} = 1;
126             }
127             else {
128 0         0 croak "use Data::Reach : unknown option : $option";
129             }
130             }
131            
132             # export the 'reach' function into caller's package, under name $export_as
133 10 100 66     722 if (! exists $seen_pkg{$pkg}) {
    50          
134 5   100     24 $export_as //= 'reach'; # default export name
135 5 100       29 if ($export_as) { # because it could be an empty string
136 6     6   5284 no strict 'refs';
  6         13  
  6         1406  
137 4         6 *{$pkg . "::" . $export_as} = \&reach;
  4         29  
138             }
139 5         2968 $seen_pkg{$pkg} = $export_as;
140             }
141             elsif ($export_as && $export_as ne $seen_pkg{$pkg}) {
142 0         0 carp "ignored request to import Data::Reach::reach as '$export_as' into "
143             . "package $pkg, because it was already imported as '$seen_pkg{$pkg}'!";
144             }
145             }
146            
147            
148             sub unimport {
149 2     2   15 my $class = shift;
150 2         6 while (my $option = shift) {
151 2         393 $^H{"Data::Reach::$option"} = '';
152             # NOTE : mark with a false value, instead of deleting from the
153             # hint hash, in order to distinguish options explicitly turned off
154             # from default options
155             }
156             }
157            
158            
159             1;
160            
161            
162             __END__