line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Tie::ShadowHash -- Merge multiple data sources into a hash. |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Copyright 1999, 2002, 2010 by Russ Allbery |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# This program is free software; you may redistribute it and/or modify it |
6
|
|
|
|
|
|
|
# under the same terms as Perl itself. |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# This module combines multiple sources of data into a single tied hash, so |
9
|
|
|
|
|
|
|
# that they can all be queried simultaneously, the source of any given |
10
|
|
|
|
|
|
|
# key-value pair irrelevant to the client script. Data sources are searched |
11
|
|
|
|
|
|
|
# in the order that they're added to the shadow hash. Changes to the hashed |
12
|
|
|
|
|
|
|
# data aren't propagated back to the actual data files; instead, they're saved |
13
|
|
|
|
|
|
|
# within the tied hash and override any data obtained from the data sources. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
############################################################################## |
16
|
|
|
|
|
|
|
# Modules and declarations |
17
|
|
|
|
|
|
|
############################################################################## |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
package Tie::ShadowHash; |
20
|
|
|
|
|
|
|
require 5.006; |
21
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
|
1179
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
52
|
|
23
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1187
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
$VERSION = '1.00'; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
############################################################################## |
28
|
|
|
|
|
|
|
# Regular methods |
29
|
|
|
|
|
|
|
############################################################################## |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# This should pretty much never be called; tie calls TIEHASH. |
32
|
|
|
|
|
|
|
sub new { |
33
|
0
|
|
|
0
|
0
|
0
|
my $class = shift; |
34
|
0
|
|
|
|
|
0
|
return $class->TIEHASH (@_); |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Given a file name and optionally a split regex, builds a hash out of the |
38
|
|
|
|
|
|
|
# contents of the file. If the split sub exists, use it to split each line |
39
|
|
|
|
|
|
|
# into an array; if the array has two elements, those are taken as the key and |
40
|
|
|
|
|
|
|
# value. If there are more, the value is an anonymous array containing |
41
|
|
|
|
|
|
|
# everything but the first. If there's no split sub, take the entire line |
42
|
|
|
|
|
|
|
# modulo the line terminator as the key and the value the number of times it |
43
|
|
|
|
|
|
|
# occurs in the file. |
44
|
|
|
|
|
|
|
sub text_source { |
45
|
4
|
|
|
4
|
0
|
8
|
my ($self, $file, $split) = @_; |
46
|
4
|
50
|
|
|
|
161
|
unless (open (HASH, '<', $file)) { |
47
|
0
|
|
|
|
|
0
|
require Carp; |
48
|
0
|
|
|
|
|
0
|
Carp::croak ("Can't open file $file: $!"); |
49
|
|
|
|
|
|
|
} |
50
|
4
|
|
|
|
|
28
|
local $_; |
51
|
4
|
|
|
|
|
6
|
my ($key, @rest, %hash); |
52
|
4
|
|
|
|
|
66
|
while () { |
53
|
48
|
|
|
|
|
58
|
chomp; |
54
|
48
|
100
|
|
|
|
78
|
if (defined $split) { |
55
|
6
|
|
|
|
|
16
|
($key, @rest) = &$split ($_); |
56
|
6
|
100
|
|
|
|
73
|
$hash{$key} = (@rest == 1) ? $rest[0] : [ @rest ]; |
57
|
|
|
|
|
|
|
} else { |
58
|
42
|
|
|
|
|
138
|
$hash{$_}++; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
} |
61
|
4
|
|
|
|
|
44
|
close HASH; |
62
|
4
|
|
|
|
|
16
|
return \%hash; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# Add data sources to the shadow hash. This takes a list of either anonymous |
66
|
|
|
|
|
|
|
# arrays (in which case the first element is the type of source and the rest |
67
|
|
|
|
|
|
|
# are arguments), filenames (in which case it's taken to be a text file with |
68
|
|
|
|
|
|
|
# each line being a key), or hash references (possibly to tied hashes). |
69
|
|
|
|
|
|
|
sub add { |
70
|
8
|
|
|
8
|
1
|
810
|
my ($self, @sources) = @_; |
71
|
8
|
|
|
|
|
17
|
for my $source (@sources) { |
72
|
9
|
100
|
|
|
|
29
|
if (ref $source eq 'ARRAY') { |
|
|
100
|
|
|
|
|
|
73
|
2
|
|
|
|
|
5
|
my ($type, @args) = @$source; |
74
|
2
|
50
|
|
|
|
5
|
if ($type eq 'text') { |
75
|
2
|
|
|
|
|
6
|
$source = $self->text_source (@args); |
76
|
|
|
|
|
|
|
} else { |
77
|
0
|
|
|
|
|
0
|
require Carp; |
78
|
0
|
|
|
|
|
0
|
Carp::croak ("Invalid source type $type"); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} elsif (!ref $source) { |
81
|
2
|
|
|
|
|
6
|
$source = $self->text_source ($source); |
82
|
|
|
|
|
|
|
} |
83
|
9
|
|
|
|
|
11
|
push (@{ $$self{SOURCES} }, $source); |
|
9
|
|
|
|
|
38
|
|
84
|
|
|
|
|
|
|
} |
85
|
8
|
|
|
|
|
30
|
return 1; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
############################################################################## |
89
|
|
|
|
|
|
|
# Tie methods |
90
|
|
|
|
|
|
|
############################################################################## |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# DELETED is a hash holding all keys that have been deleted; it's checked |
93
|
|
|
|
|
|
|
# first on any access. EACH is a pointer to the current structure being |
94
|
|
|
|
|
|
|
# traversed on an "each" of the shadow hash, so that they can all be traversed |
95
|
|
|
|
|
|
|
# in order. OVERRIDE is a hash containing values set directly by the user, |
96
|
|
|
|
|
|
|
# which override anything in the shadow hash's underlying data structures. |
97
|
|
|
|
|
|
|
# And finally, SOURCES is an array of the data structures (all Perl hashes, |
98
|
|
|
|
|
|
|
# possibly tied). |
99
|
|
|
|
|
|
|
sub TIEHASH { |
100
|
2
|
|
|
2
|
|
1582
|
my $class = shift; |
101
|
2
|
|
33
|
|
|
15
|
$class = ref $class || $class; |
102
|
2
|
|
|
|
|
11
|
my $self = { |
103
|
|
|
|
|
|
|
DELETED => {}, |
104
|
|
|
|
|
|
|
EACH => -1, |
105
|
|
|
|
|
|
|
OVERRIDE => {}, |
106
|
|
|
|
|
|
|
SOURCES => [] |
107
|
|
|
|
|
|
|
}; |
108
|
2
|
|
|
|
|
5
|
bless ($self, $class); |
109
|
2
|
50
|
|
|
|
11
|
$self->add (@_) if @_; |
110
|
2
|
|
|
|
|
7
|
return $self; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Note that this doesn't work quite right in the case of keys with undefined |
114
|
|
|
|
|
|
|
# values, but we can't make it work right since that would require using |
115
|
|
|
|
|
|
|
# exists and a lot of common data sources (such as NDBM_File tied hashes) |
116
|
|
|
|
|
|
|
# don't implement exists. |
117
|
|
|
|
|
|
|
sub FETCH { |
118
|
23
|
|
|
23
|
|
4378
|
my ($self, $key) = @_; |
119
|
23
|
100
|
|
|
|
66
|
return if $self->{DELETED}{$key}; |
120
|
22
|
100
|
|
|
|
72
|
return $self->{OVERRIDE}{$key} if exists $self->{OVERRIDE}{$key}; |
121
|
16
|
|
|
|
|
18
|
for my $source (@{ $self->{SOURCES} }) { |
|
16
|
|
|
|
|
31
|
|
122
|
18
|
100
|
|
|
|
143
|
return $source->{$key} if defined $source->{$key}; |
123
|
|
|
|
|
|
|
} |
124
|
3
|
|
|
|
|
11
|
return; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub STORE { |
128
|
8
|
|
|
8
|
|
1183
|
my ($self, $key, $value) = @_; |
129
|
8
|
|
|
|
|
17
|
delete $self->{DELETED}{$key}; |
130
|
8
|
|
|
|
|
32
|
$self->{OVERRIDE}{$key} = $value; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub DELETE { |
134
|
5
|
|
|
5
|
|
1681
|
my ($self, $key) = @_; |
135
|
5
|
|
|
|
|
10
|
delete $self->{OVERRIDE}{$key}; |
136
|
5
|
|
|
|
|
20
|
$self->{DELETED}{$key} = 1; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub CLEAR { |
140
|
5
|
|
|
5
|
|
1704
|
my ($self) = @_; |
141
|
5
|
|
|
|
|
10
|
$self->{DELETED} = {}; |
142
|
5
|
|
|
|
|
12
|
$self->{OVERRIDE} = {}; |
143
|
5
|
|
|
|
|
11
|
$self->{SOURCES} = []; |
144
|
5
|
|
|
|
|
23
|
$self->{EACH} = -1; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# This could throw an exception if any underlying source doesn't support |
148
|
|
|
|
|
|
|
# exists (like NDBM_File). |
149
|
|
|
|
|
|
|
sub EXISTS { |
150
|
2
|
|
|
2
|
|
387
|
my ($self, $key) = @_; |
151
|
2
|
100
|
|
|
|
12
|
return if exists $self->{DELETED}{$key}; |
152
|
1
|
|
|
|
|
3
|
for my $source ($self->{OVERRIDE}, @{ $self->{SOURCES} }) { |
|
1
|
|
|
|
|
3
|
|
153
|
2
|
50
|
|
|
|
8
|
return 1 if exists $source->{$key}; |
154
|
|
|
|
|
|
|
} |
155
|
1
|
|
|
|
|
8
|
return; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# We have to reset the each counter on all hashes. For tied hashes, we call |
159
|
|
|
|
|
|
|
# FIRSTKEY directly because it's potentially more efficient than calling keys |
160
|
|
|
|
|
|
|
# on the hash. |
161
|
|
|
|
|
|
|
sub FIRSTKEY { |
162
|
11
|
|
|
11
|
|
38
|
my ($self) = @_; |
163
|
11
|
|
|
|
|
12
|
keys %{ $self->{OVERRIDE} }; |
|
11
|
|
|
|
|
23
|
|
164
|
11
|
|
|
|
|
14
|
for my $source (@{ $self->{SOURCES} }) { |
|
11
|
|
|
|
|
637
|
|
165
|
20
|
|
|
|
|
22
|
my $tie = tied $source; |
166
|
20
|
50
|
|
|
|
30
|
if ($tie) { |
167
|
0
|
|
|
|
|
0
|
$tie->FIRSTKEY; |
168
|
|
|
|
|
|
|
} else { |
169
|
20
|
|
|
|
|
33
|
keys %$source; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
} |
172
|
11
|
|
|
|
|
22
|
$self->{EACH} = -1; |
173
|
11
|
|
|
|
|
22
|
return $self->NEXTKEY; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Walk the sources by calling each on each one in turn, skipping deleted |
177
|
|
|
|
|
|
|
# keys and keys shadowed by earlier hashes and using $self->{EACH} to |
178
|
|
|
|
|
|
|
# store the number of source we're at. |
179
|
|
|
|
|
|
|
sub NEXTKEY { |
180
|
274
|
|
|
274
|
|
333
|
my ($self) = @_; |
181
|
274
|
|
|
|
|
307
|
my @result = (); |
182
|
317
|
|
|
|
|
882
|
SOURCE: |
183
|
274
|
|
66
|
|
|
574
|
while (!@result && $self->{EACH} < @{ $self->{SOURCES} }) { |
184
|
307
|
100
|
|
|
|
491
|
if ($self->{EACH} == -1) { |
185
|
17
|
|
|
|
|
15
|
@result = each %{ $self->{OVERRIDE} }; |
|
17
|
|
|
|
|
34
|
|
186
|
|
|
|
|
|
|
} else { |
187
|
290
|
|
|
|
|
276
|
@result = each %{ $self->{SOURCES}[$self->{EACH}] }; |
|
290
|
|
|
|
|
1257
|
|
188
|
|
|
|
|
|
|
} |
189
|
307
|
100
|
100
|
|
|
1240
|
if (@result && $self->{DELETED}{$result[0]}) { |
190
|
4
|
|
|
|
|
7
|
undef @result; |
191
|
4
|
|
|
|
|
10
|
next; |
192
|
|
|
|
|
|
|
} |
193
|
303
|
100
|
100
|
|
|
1013
|
if (@result && $self->{EACH} > -1) { |
194
|
267
|
|
|
|
|
279
|
my $key = $result[0]; |
195
|
267
|
100
|
|
|
|
480
|
if (exists $self->{OVERRIDE}{$key}) { |
196
|
7
|
|
|
|
|
12
|
undef @result; |
197
|
7
|
|
|
|
|
21
|
next; |
198
|
|
|
|
|
|
|
} |
199
|
260
|
|
|
|
|
537
|
for (my $index = $self->{EACH} - 1; $index >= 0; $index--) { |
200
|
126
|
100
|
|
|
|
834
|
if (defined $self->{SOURCES}[$index]{$key}) { |
201
|
2
|
|
|
|
|
3
|
undef @result; |
202
|
2
|
|
|
|
|
6
|
next SOURCE; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
} |
206
|
294
|
50
|
|
|
|
1215
|
return (wantarray ? @result : $result[0]) if @result; |
|
|
100
|
|
|
|
|
|
207
|
30
|
|
|
|
|
67
|
$self->{EACH}++; |
208
|
|
|
|
|
|
|
} |
209
|
10
|
|
|
|
|
81
|
return; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
############################################################################## |
213
|
|
|
|
|
|
|
# Module return value and documentation |
214
|
|
|
|
|
|
|
############################################################################## |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# Make sure the module returns true. |
217
|
|
|
|
|
|
|
1; |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
__DATA__ |