File Coverage

blib/lib/Hash/Typed.pm
Criterion Covered Total %
statement 103 110 93.6
branch 31 34 91.1
condition 19 20 95.0
subroutine 15 17 88.2
pod 0 3 0.0
total 168 184 91.3


line stmt bran cond sub pod time code
1             package Hash::Typed;
2 5     5   289498 use strict; use warnings; our $VERSION = '0.04';
  5     5   42  
  5         120  
  5         22  
  5         6  
  5         208  
3 5     5   26 use Carp qw/croak/; use Tie::Hash; our (@ISA);
  5     5   6  
  5         223  
  5         2081  
  5         4262  
  5         267  
4              
5             BEGIN {
6 5     5   5530 @ISA = qw(Tie::Hash);
7             }
8              
9             sub new {
10 6     6 0 833 my ($package) = (shift);
11              
12 6         11 my $self = { };
13            
14 6         9 tie %{$self}, 'Hash::Typed', @_;
  6         25  
15              
16 5         19 bless $self, $package;
17             }
18              
19             sub TIEHASH {
20 39     39   2761 my ($pkg) = shift;
21 39         50 my($self) = [];
22 39         44 push @{$self}, {}, [], [], 0;
  39         98  
23 39         66 $self = bless $self, $pkg;
24 39 100       82 if (ref $_[0]) {
25 13         38 my $spec = $self->PARSE(shift);
26 13         17 push @{$self}, $spec;
  13         21  
27             }
28 39         76 while (@_) {
29 45         82 $self->STORE(shift, shift);
30             }
31 36 100 100     95 if ($self->[4] && $self->[4]->{required}) {
32 2 100       4 if (ref $self->[4]->{required}) {
33 1         4 $self->REQUIRED($self->[4]->{required});
34             } else {
35 1         2 $self->REQUIRED([keys %{$self->[4]->{ordered_keys}}]);
  1         3  
36             }
37             }
38 36         77 return $self;
39             }
40              
41             sub FETCH {
42 350     350   593 my($self, $key) = (shift, shift);
43 350 100       1301 return exists( $self->[0]{$key} ) ? $self->[2][ $self->[0]{$key} ] : undef;
44             }
45              
46             sub STORE {
47 126     126   2591 my ($self, $key, $value) = @_;
48              
49 126 100       221 if ($self->[4]) {
50 51         119 my $described = $self->[4]->{keys}->{$key};
51              
52 51 100 100     117 if ($self->[4]->{strict} && !$described) {
53 3         646 croak "Strict mode enabled and passed key \"${key}\" does not exist in the specification.";
54             }
55              
56 48 100       230 $value = $described->($value)
57             if ($described);
58             }
59              
60 120 100 100     1188 if (exists $self->[0]{$key}) {
    100 100        
61 3         8 my($i) = $self->[0]{$key};
62 3         8 $self->[1][$i] = $key;
63 3         6 $self->[2][$i] = $value;
64 3         9 $self->[0]{$key} = $i;
65 39         101 } elsif ($self->[4] && defined $self->[4]{ordered_keys}{$key} && $self->[4]{ordered_keys}{$key} <= scalar @{$self->[1]}) {
66 34         66 my $i = $self->[4]{ordered_keys}{$key};
67 34         66 my $before = $self->[1]->[$i - 1];
68 34 100 100     84 $i = $i == 0 ? $i : --$i if ($before && ($self->[4]{ordered_keys}{$before} || -1) >= $i);
    100 100        
69 34         52 splice(@{$self->[1]}, $i, 0, $key);
  34         68  
70 34         48 splice(@{$self->[2]}, $i, 0, $value);
  34         46  
71 34         52 $self->[0]{$key} = $i;
72             $self->[0]{ $self->[1][$_] }++
73 34         46 for ($i+1..$#{$self->[1]});
  34         126  
74             } else {
75 83         84 push(@{$self->[1]}, $key);
  83         124  
76 83         86 push(@{$self->[2]}, $value);
  83         103  
77 83         83 $self->[0]{$key} = $#{$self->[1]};
  83         207  
78             }
79             }
80              
81             sub DELETE {
82 3     3   932 my ($self, $key) = @_;
83              
84 3 50       20 if (exists $self->[0]{$key}) {
85 3         9 my($i) = $self->[0]{$key};
86             $self->[0]{ $self->[1][$_] }--
87 3         7 for ($i+1..$#{$self->[1]});
  3         11  
88 3 50       10 $self->[3]-- if ( $i == $self->[3]-1 );
89 3         8 delete $self->[0]{$key};
90 3         3 splice @{$self->[1]}, $i, 1;
  3         8  
91 3         7 return (splice(@{$self->[2]}, $i, 1))[0];
  3         11  
92             }
93 0         0 return undef;
94             }
95              
96             sub CLEAR {
97 0     0   0 my ($self) = @_;
98 0         0 push @{$self}, {}, [], [], 0;
  0         0  
99             }
100              
101 29     29   528 sub EXISTS { exists $_[0]->[0]{ $_[1] }; }
102              
103             sub FIRSTKEY {
104 26     26   2561 $_[0][3] = 0;
105 26         37 &NEXTKEY;
106             }
107              
108             sub NEXTKEY {
109 104 100   104   127 return $_[0][1][ $_[0][3]++ ] if ($_[0][3] <= $#{ $_[0][1] } );
  104         317  
110 26         58 return undef;
111             }
112              
113 0     0   0 sub SCALAR { scalar(@{$_[0]->[1]}); }
  0         0  
114              
115             sub PARSE {
116 26     26 0 38 my ($self, $spec) = @_;
117 26         33 my (%keys, %described);
118 26         61 tie(%described, 'Hash::Typed');
119 26         36 while (@{$spec}) {
  88         148  
120 62         64 my ($key, $value) = (shift @{$spec}, shift @{$spec});
  62         84  
  62         94  
121 62 100 66     141 if ($key eq 'keys' && ref $value eq 'ARRAY') {
122 13         37 ($value) = $self->PARSE($value);
123 13         18 my $i = 0;
124 13         17 %keys = map { $_ => $i++ } keys %{$value};
  39         111  
  13         45  
125             }
126 62         181 $described{$key} = $value;
127             }
128 26 100       83 $described{ordered_keys} = \%keys if scalar keys %keys;
129 26         65 return \%described;
130             }
131              
132             sub REQUIRED {
133 2     2 0 3 my ($self, $keys) = @_;
134 2         3 for my $key (@{$keys}) {
  2         4  
135 7 50       12 if (! defined $self->[0]{$key}) {
136 0           croak "Required key $key not set.";
137             }
138             }
139             }
140              
141             1;
142              
143             __END__;