File Coverage

blib/lib/Tie/History.pm
Criterion Covered Total %
statement 151 203 74.3
branch 66 140 47.1
condition 10 15 66.6
subroutine 29 35 82.8
pod 6 7 85.7
total 262 400 65.5


line stmt bran cond sub pod time code
1             package Tie::History;
2              
3 1     1   33804 use 5.008; # Data::Dumper's use of Sortkeys requires 5.8 or higher
  1         4  
  1         36  
4 1     1   5 use strict;
  1         1  
  1         25  
5 1     1   13 use warnings;
  1         5  
  1         23  
6 1     1   4 use warnings::register;
  1         3  
  1         179  
7 1     1   1156 use Data::Dumper;
  1         10759  
  1         66  
8 1     1   9 use Carp;
  1         2  
  1         2182  
9              
10             our $VERSION = '0.03';
11              
12             sub TIESCALAR {
13 2     2   15 my $self = shift;
14 2         4 my $args = shift;
15              
16 2 50       6 if ($args) {
17 0 0       0 unless (ref $args eq 'HASH') {
18 0         0 croak('->TIESCALAR: First argument to TIESCALAR constructor should be a hash reference');
19             }
20             }
21              
22 2   50     26 my $data = {
23             CURRENT => "",
24             PREVIOUS => [],
25             RECENT => 1, # not really, but we want to prevent a commit on nothing
26             ENTRYS => 0,
27             TYPE => "SCALAR",
28             AUTOCOMMIT => $args->{AutoCommit} || 0,
29             };
30 2         16 return bless $data, $self;
31             }
32              
33             sub TIEARRAY {
34 2     2   9 my $self = shift;
35 2         4 my $args = shift;
36              
37 2 50       7 if ($args) {
38 0 0       0 unless (ref $args eq 'HASH') {
39 0         0 croak('->TIESCALAR: First argument to TIESCALAR constructor should be a hash reference');
40             }
41             }
42              
43 2   50     22 my $data = {
44             CURRENT => [],
45             PREVIOUS => [],
46             RECENT => 1, # not really, but we want to prevent a commit on nothing
47             ENTRYS => 0,
48             TYPE => "ARRAY",
49             AUTOCOMMIT => $args->{AutoCommit} || 0,
50             };
51 2         12 return bless $data, $self;
52             }
53              
54             sub TIEHASH {
55 2     2   12 my $self = shift;
56 2         4 my $args = shift;
57              
58 2 50       6 if ($args) {
59 0 0       0 unless (ref $args eq 'HASH') {
60 0         0 croak('->TIESCALAR: First argument to TIESCALAR constructor should be a hash reference');
61             }
62             }
63              
64 2   50     34 my $data = {
65             CURRENT => {},
66             PREVIOUS => [],
67             RECENT => 1, # not really, but we want to prevent a commit on nothing
68             ENTRYS => 0,
69             TYPE => "HASH",
70             AUTOCOMMIT => $args->{AutoCommit} || 0,
71             };
72 2         12 return bless $data, $self;
73             }
74              
75             sub commit {
76 18     18 1 870 my $self = shift;
77 18 100       62 if ($self->{TYPE} eq "SCALAR") {
    100          
    50          
78 6 100 100     32 $self->{RECENT} = ($self->{CURRENT} eq ($self->{PREVIOUS}->[-1] || "")) ? 1 : 0;
79             }
80             elsif ($self->{TYPE} eq "ARRAY") {
81 6         15 $self->_cmp;
82 6 100       360 $self->{RECENT} = 1 if (scalar(@{$self->{CURRENT}}) == 0);
  6         21  
83             }
84             elsif ($self->{TYPE} eq "HASH") {
85 6         14 $self->_cmp;
86 6 100       297 $self->{RECENT} = 1 if (scalar(keys(%{$self->{CURRENT}})) == 0);
  6         22  
87             }
88 18 100       41 if ($self->{RECENT} == 1) {
89 9 50       535 carp "You can't commit something that has not changed" if (warnings::enabled());
90 9         20 return 0;
91             }
92             else {
93 9 100       20 if ($self->{TYPE} eq "HASH") {
94 3         5 push(@{$self->{PREVIOUS}}, {%{$self->{CURRENT}}});
  3         5  
  3         11  
95             }
96             else{
97 6         6 push(@{$self->{PREVIOUS}}, $self->{CURRENT});
  6         13  
98             }
99 9         16 $self->{RECENT} = 1;
100 9         13 $self->{ENTRYS}++;
101 9         24 return 1;
102             }
103             }
104              
105             sub setautocommit {
106 0     0 0 0 my $self = shift;
107 0         0 my $value = shift;
108 0 0       0 if ($value == 0) {
    0          
109 0         0 $self->{AUTOCOMMIT} = 0;
110             }
111             elsif ($value == 1) {
112 0         0 $self->{AUTOCOMMIT} = 1;
113             }
114             else {
115 0         0 croak " ->setautocommit takes either 1 or 0";
116             }
117             }
118              
119             sub _cmp {
120 12     12   13 my $self = shift;
121 12         18 my $current = $self->{CURRENT};
122 12   100     44 my $previous = $self->{PREVIOUS}->[-1] || "";
123 12         56 my $c = new Data::Dumper([$current])->Deepcopy(1)->Terse(1)->Purity(1);
124 12         447 my $p = new Data::Dumper([$previous])->Deepcopy(1)->Terse(1)->Purity(1);
125 12 100       353 $c->Sortkeys(1) if ($self->{TYPE} eq "HASH");
126 12 100       57 $p->Sortkeys(1) if ($self->{TYPE} eq "HASH");
127 12 100       43 $self->{RECENT} = ($p->Dump eq $c->Dump) ? 1 : 0;
128             }
129              
130             sub previous {
131 6     6 1 25 my $self = shift;
132 6 100       13 return 0 unless $self->_checkentries();
133 3 50       11 my $index = ($self->{RECENT}) ? -2 : -1;
134 3         18 return $self->{PREVIOUS}->[$index];
135             }
136              
137             sub current {
138 3     3 1 6 my $self = shift;
139 3         16 return $self->{CURRENT};
140             }
141              
142             sub getall {
143 3     3 1 8 my $self = shift;
144 3 50       24 return 0 unless $self->_checkentries();
145 3         23 return $self->{PREVIOUS};
146             }
147              
148             sub get {
149 6     6 1 11 my $self = shift;
150 6         7 my $index = shift;
151 6 50       15 return 0 unless $self->_checkentries($index);
152 6         34 return $self->{PREVIOUS}->[$index];
153             }
154              
155             sub revert {
156 1     1 1 2 my $self = shift;
157 1 50 33     11 my $index = shift || ($self->{RECENT}) ? -2 : -1;
158 1 50       3 return 0 unless $self->_checkentries($index);
159 1         3 $self->{CURRENT} = $self->{PREVIOUS}->[$index];
160 1         4 return 1;
161             }
162              
163             sub _checkentries {
164 16     16   17 my $self = shift;
165 16   100     61 my $index = shift || "NULL";
166 16 100       43 if ($self->{ENTRYS} == 0) {
167 3 50       293 carp "There are no previous entries" if (warnings::enabled());
168 3         12 return 0;
169             }
170 13 100       36 if ($index ne "NULL") {
171 4 50       13 if ($index >= $self->{ENTRYS}) {
172 0 0       0 carp "Invalid entry" if (warnings::enabled());
173 0         0 return 0;
174             }
175             }
176 13         46 return 1;
177             }
178              
179             sub FETCH {
180 4     4   41 my $self = shift;
181 4 50       13 confess "I am not a class method" unless ref $self;
182 4         6 my $indexkey = shift;
183 4 100       18 return $self->{CURRENT} if ($self->{TYPE} eq "SCALAR");
184 1 50       6 return $self->{CURRENT}->[$indexkey] if ($self->{TYPE} eq "ARRAY");
185 1 50       7 return $self->{CURRENT}->{$indexkey} if ($self->{TYPE} eq "HASH");
186             }
187              
188             sub STORE {
189 30     30   1101 my $self = shift;
190 30 50       70 confess "I am not a class method" unless ref $self;
191 30 100       95 if ($self->{TYPE} eq "SCALAR") {
    100          
    50          
192 4         6 my $value = shift;
193 4         15 return $self->{CURRENT} = $value;
194             }
195             elsif ($self->{TYPE} eq "ARRAY") {
196 22         23 my $index = shift;
197 22         26 my $value = shift;
198 22         98 return $self->{CURRENT}->[$index] = $value;
199             }
200             elsif ($self->{TYPE} eq "HASH") {
201 4         9 my $key = shift;
202 4         5 my $value = shift;
203 4         19 return $self->{CURRENT}->{$key} = $value;
204             }
205 0 0       0 if ($self->{AUTOCOMMIT}) {
206 0         0 $self->commit;
207             }
208             }
209              
210             sub UNTIE {
211 0     0   0 my $self = shift;
212 0 0       0 confess "I am not a class method" unless ref $self;
213 0         0 undef($self->{PREVIOUS});
214 0 0       0 return $self->{CURRENT} if ($self->{TYPE} eq "SCALAR");
215 0 0       0 return @{$self->{CURRENT}} if ($self->{TYPE} eq "ARRAY");
  0         0  
216 0 0       0 return %{$self->{CURRENT}} if ($self->{TYPE} eq "HASH");
  0         0  
217             }
218              
219             sub DESTROY {
220 6     6   875 my $self = shift;
221 6 50       154 confess "I am not a class method" unless ref $self;
222             }
223              
224             sub EXISTS {
225 0     0   0 my $self = shift;
226 0 0       0 confess "I am not a class method" unless ref $self;
227 0         0 my $key = shift;
228 0 0       0 if ($self->{TYPE} eq "ARRAY") {
    0          
229 0 0       0 return 0 if (!defined $self->{CURRENT}->[$key]);
230 0         0 return 1;
231             }
232             elsif ($self->{TYPE} eq "HASH") {
233 0         0 return exists $self->{CURRENT}->{$key};
234             }
235             }
236              
237             sub CLEAR {
238 5     5   614 my $self = shift;
239 5 50       13 confess "I am not a class method" unless ref $self;
240 5 50       33 return $self->{CURRENT} = [] if ($self->{TYPE} eq "ARRAY");
241 0 0       0 return %{$self->{CURRENT}} = () if ($self->{TYPE} eq "HASH");
  0         0  
242             }
243              
244             sub DELETE {
245 0     0   0 my $self = shift;
246 0 0       0 confess "I am not a class method" unless ref $self;
247 0         0 my $key = shift;
248 0 0       0 return $self->STORE($key, undef) if ($self->{TYPE} eq "ARRAY");
249 0 0       0 return delete $self->{CURRENT}->{$key} if ($self->{TYPE} eq "HASH");
250             }
251              
252             sub FIRSTKEY {
253 0     0   0 my $self = shift;
254 0 0       0 confess "I am not a class method" unless ref $self;
255 0         0 my $a = scalar keys %{$self->{CURRENT}};
  0         0  
256 0         0 each %{$self->{CURRENT}};
  0         0  
257             }
258              
259             sub NEXTKEY {
260 0     0   0 my $self = shift;
261 0 0       0 confess "I am not a class method" unless ref $self;
262 0         0 my $lastkey = shift;
263 0         0 each %{$self->{CURRENT}}
  0         0  
264             }
265              
266             sub FETCHSIZE {
267 17     17   19 my $self = shift;
268 17 50       34 confess "I am not a class method" unless ref $self;
269 17         18 return scalar(@{$self->{CURRENT}});
  17         91  
270             }
271              
272             sub STORESIZE {
273 5     5   7 my $self = shift;
274 5 50       21 confess "I am not a class method" unless ref $self;
275 5         6 my $count = shift;
276 5 50       11 if ($count > $self->FETCHSIZE()) {
    0          
277 5         10 foreach ($count - $self->FETCHSIZE() .. $count - 1) {
278 0         0 $self->STORE($_, undef);
279             }
280             }
281             elsif ($count < $self->FETCHSIZE()) {
282 0         0 foreach (0 .. $self->FETCHSIZE() - $count - 2) {
283 0         0 $self->POP();
284             }
285             }
286             }
287              
288             sub EXTEND {
289 5     5   10 my $self = shift;
290 5 50       12 confess "I am not a class method" unless ref $self;
291 5         7 my $count = shift;
292 5         14 $self->STORESIZE($count);
293             }
294              
295             sub PUSH {
296 1     1   3 my $self = shift;
297 1 50       4 confess "I am not a class method" unless ref $self;
298 1         3 my @list = @_;
299 1         3 push(@{$self->{CURRENT}}, @list);
  1         3  
300 1         3 return $self->FETCHSIZE();
301             }
302              
303             sub POP {
304 1     1   3 my $self = shift;
305 1 50       5 confess "I am not a class method" unless ref $self;
306 1         2 return pop @{$self->{CURRENT}};
  1         5  
307             }
308              
309             sub SHIFT {
310 1     1   2 my $self = shift;
311 1 50       5 confess "I am not a class method" unless ref $self;
312 1         2 return shift @{$self->{CURRENT}};
  1         5  
313             }
314              
315             sub UNSHIFT {
316 1     1   2 my $self = shift;
317 1 50       5 confess "I am not a class method" unless ref $self;
318 1         3 my @list = @_;
319 1         2 unshift(@{$self->{CURRENT}}, @list);
  1         3  
320 1         3 return $self->FETCHSIZE();
321             }
322              
323             sub SPLICE {
324 2     2   10 my $self = shift;
325 2 50       7 confess "I am not a class method" unless ref $self;
326 2         5 my $size = $self->FETCHSIZE;
327 2 50       6 my $offset = @_ ? shift : 0;
328 2 100       7 $offset += $size if $offset < 0;
329 2 100       6 my $length = @_ ? shift : $size-$offset;
330 2         3 return splice(@{$self->{CURRENT}},$offset,$length,@_);
  2         11  
331             }
332              
333             1;
334             __END__