File Coverage

blib/lib/Tie/Subset/Hash/Masked.pm
Criterion Covered Total %
statement 66 67 100.0
branch 23 24 100.0
condition 3 3 100.0
subroutine 14 14 100.0
pod n/a
total 106 108 100.0


line stmt bran cond sub pod time code
1             #!perl
2             package Tie::Subset::Hash::Masked;
3 2     2   111991 use warnings;
  2         15  
  2         77  
4 2     2   13 use strict;
  2         4  
  2         40  
5 2     2   9 use warnings::register;
  2         5  
  2         238  
6 2     2   13 use Carp;
  2         4  
  2         1799  
7              
8             # For AUTHOR, COPYRIGHT, AND LICENSE see the bottom of this file
9              
10             =head1 Name
11              
12             Tie::Subset::Hash::Masked - Tie a hash to mask some of its keys
13              
14             =head1 Synopsis
15              
16             use Tie::Subset::Hash::Masked;
17             use Data::Dumper;
18             my %hash = ( foo=>11, bar=>22, quz=>33 );
19             tie my %masked, 'Tie::Subset::Hash::Masked', \%hash, ['bar','quz'];
20             print Dumper(\%masked); # shows only { foo => 11 }
21             $masked{baz}++; # adds this key to %masked and %hash
22              
23             =head1 Description
24              
25             This class for tied hashes provides a masked "view" of a hash.
26              
27             =over
28              
29             =cut
30              
31             our $VERSION = '0.02';
32              
33             =item Cing
34              
35             tie my %masked, 'Tie::Subset::Hash::Masked', \%hash, \@mask;
36              
37             You must specify which keys from the original hash should be masked
38             in the tied hash. (Keys that do not yet exist in the original hash
39             may also be specified.)
40              
41             =cut
42              
43             sub TIEHASH { ## no critic (RequireArgUnpacking)
44 7 100   7   2542 @_==3 or croak "bad number of arguments to tie";
45 6         19 my ($class, $hash, $mask) = @_;
46 6 100       105 ref $hash eq 'HASH' or croak "must provide hashref to tie";
47 5 100       101 ref $mask eq 'ARRAY' or croak "must provide key list to mask";
48 4 100       12 for (@$mask) { croak "bad hash key '$_'" if ref; croak "bad hash key undef" if !defined }
  13 100       116  
  12         122  
49 2         8 my $self = { hash => $hash, mask => { map {$_=>1} @$mask } };
  11         28  
50 2         11 return bless $self, $class;
51             }
52              
53             =item Fetching
54              
55             If the key is masked, returns nothing (undef), otherwise, the value
56             from the underlying hash is returned.
57              
58             =cut
59              
60             sub FETCH {
61 47     47   5250 my ($self,$key) = @_;
62 47 100       108 return if exists $self->{mask}{$key};
63 41         115 return $self->{hash}{$key};
64             }
65              
66             =item Storing
67              
68             If the key is masked, the operation is ignored and a warning issued,
69             otherwise, the new value will be stored in the underlying hash.
70              
71             =cut
72              
73             sub STORE {
74 5     5   1611 my ($self,$key,$val) = @_;
75 5 100       19 if (not exists $self->{mask}{$key}) {
76 3         17 return $self->{hash}{$key} = $val;
77             } # else
78 2         383 warnings::warnif("assigning to masked key '$key' not (yet) supported in ".ref($self).", ignoring");
79 2         123 return;
80             }
81              
82             =item C
83              
84             Will return true only if the key exists in the underlying hash I
85             the key is not masked.
86              
87             =cut
88              
89             sub EXISTS {
90 37     37   419 my ($self,$key) = @_;
91             # need to write this in this slightly strange way because otherwise
92             # the code coverage tool isn't picking it up correctly...
93 37 100 100     140 if ( !exists $self->{mask}{$key} && exists $self->{hash}{$key} )
94 34         82 { return !!1 } else { return !!0 }
  3         15  
95             }
96              
97             =item Iterating (C, C, etc.)
98              
99             Only keys that exist in the underlying hash I that aren't masked
100             are iterated over. The iterator of the underlying hash is utilized,
101             so iterating over the tied hash will affect the state of the iterator
102             of the underlying hash.
103              
104             =cut
105              
106             sub FIRSTKEY {
107 8     8   3120 my ($self) = @_;
108 8         12 my $dummy = keys %{$self->{hash}}; # reset iterator
  8         25  
109 8         19 return $self->NEXTKEY;
110             }
111             sub NEXTKEY {
112 46     46   73 my ($self,$lkey) = @_;
113 46         57 my $next;
114             SEEK: {
115 46         55 $next = each %{$self->{hash}};
  73         91  
  73         109  
116 73 100       165 return unless defined $next;
117 65 100       122 redo SEEK if exists $self->{mask}{$next};
118             }
119 38         84 return $next;
120             }
121              
122             =item Cing
123              
124             If the key is masked, the operation is ignored and a warning issued,
125             otherwise, the key will be deleted from the underlying hash.
126              
127             =cut
128              
129             sub DELETE {
130 6     6   2326 my ($self,$key) = @_;
131 6 100       18 if (not exists $self->{mask}{$key}) {
132 4         21 return delete $self->{hash}{$key};
133             } # else
134 2         242 warnings::warnif("deleting masked key '$key' not (yet) supported in ".ref($self).", ignoring");
135 2         97 return;
136             }
137              
138             =item Clearing
139              
140             Not (yet) supported (because it is ambiguous whether this operation
141             should delete keys from the underlying hash or not). Attempting to
142             clear the tied hash currently does nothing and causes a warning
143             to be issued.
144              
145             A future version of this module may lift this limitation (if a
146             useful default behavior exists).
147              
148             =cut
149              
150             sub CLEAR {
151 1     1   402 my ($self) = @_;
152 1         116 warnings::warnif("clearing of ".ref($self)." not (yet) supported, ignoring");
153 1         37 return;
154             }
155              
156             sub SCALAR {
157 1     1   3 my ($self) = @_;
158             # I'm not sure why the following counts as two statements in the coverage tool
159             # uncoverable branch true
160             # uncoverable statement count:2
161 1 50       4 return scalar %{$self->{hash}} if $] lt '5.026';
  0         0  
162 1         2 my %keys = map {$_=>1} keys %{$self->{hash}};
  6         13  
  1         5  
163 1         2 delete @keys{ keys %{$self->{mask}} };
  1         5  
164 1         6 return scalar keys %keys;
165             }
166              
167             sub UNTIE {
168 1     1   317 my ($self) = @_;
169 1         3 $self->{hash} = undef;
170 1         3 $self->{mask} = undef;
171 1         9 return;
172             }
173              
174             1;
175             __END__