File Coverage

blib/lib/Hash/Convert.pm
Criterion Covered Total %
statement 106 109 97.2
branch 40 44 90.9
condition 13 18 72.2
subroutine 18 18 100.0
pod 5 7 71.4
total 182 196 92.8


line stmt bran cond sub pod time code
1             package Hash::Convert;
2 13     13   99305 use 5.008005;
  13         199  
  13         2734  
3 13     13   76 use strict;
  13         27  
  13         472  
4 13     13   76 use warnings;
  13         23  
  13         645  
5              
6 13     13   70 use Carp qw(croak);
  13         23  
  13         21214  
7              
8             our $VERSION = "0.04";
9              
10             my $allow_combine = [
11             [qw/from/],
12             [qw/from default/],
13              
14             [qw/from via/],
15             [qw/from via default/],
16              
17             [qw/contain/],
18             [qw/contain default/],
19              
20             [qw/define/],
21             ];
22              
23             sub new {
24 28     28 0 142552 my ($class, $rules, $opts) = @_;
25              
26 28         136 my $self = bless {
27             rules => $rules,
28             }, $class;
29              
30 28         105 $self->_prepare($rules, $opts);
31              
32 27         84 $self;
33             }
34              
35             sub _validate_cmd {
36 47     47   77 my ($self, $cmd_map) = @_;
37              
38 47         204 for my $combine (@{$allow_combine}) {
  47         99  
39 107         178 my $valid = [grep { $cmd_map->{$_} } @$combine];
  167         363  
40              
41 107 100       348 if ( scalar @$valid == scalar keys %$cmd_map ) {
42 47         207 return 1;
43             }
44             }
45 0         0 return 0;
46             }
47              
48             sub _prepare_opts {
49 36     36   64 my ($self, $rules, $opts) = @_;
50              
51 36 100       179 if (my $pass = $opts->{pass}) {
52 2 100       10 $pass = [$pass] unless (ref $pass);
53 2         4 for my $name (@$pass) {
54 3         13 $rules->{$name} = { from => $name };
55             }
56             }
57             }
58              
59             sub _prepare {
60 36     36   65 my ($self, $rules, $opts) = @_;
61              
62 36         99 $self->_prepare_opts($rules, $opts);
63              
64 36         177 for my $name (sort keys %$rules) {
65 47         80 my $rule = $rules->{$name};
66 47         65 my %cmds = map { $_ => 1 } keys %{$rule};
  63         353  
  47         131  
67              
68 47 50       156 unless ($self->_validate_cmd(\%cmds)) {
69 0         0 croak sprintf "%s rules invalid combinations (%s)", $name, join(',', sort keys %cmds);
70             }
71 47 100 100     315 if ($cmds{from} && not $cmds{via}) {
72 32 100 66     131 if ( (ref $rule->{from} eq 'ARRAY') && (scalar @{$rule->{from}} != 1) ) {
  1         5  
73 1         3 croak sprintf "multiple value allowed only 'via' rule. ( from => [%s] )", join(', ', map { "'$_'" } @{$rule->{from}} );
  2         36  
  1         4  
74             }
75             }
76              
77 46 100       125 if ($cmds{contain}) {
78 8         41 $self->_prepare($rule->{contain});
79             }
80             else {
81 38 100 100     363 $rule->{from} = [$rule->{from}] if ($rule->{from} && ref $rule->{from} ne 'ARRAY');
82             }
83             }
84              
85             }
86              
87             sub convert {
88 27     27 1 142 my ($self, @before) = @_;
89              
90 27 100 66     290 if (@before && scalar @before == 1 && ref $before[0] eq 'HASH') {
    50 66        
      33        
91 25         132 my $after = $self->_process($self->{rules}, $before[0]);
92 25         77 return $after;
93             }
94             elsif (@before && scalar @before % 2 == 0) {
95 2         7 my %hash = @before;
96 2         14 my $after = $self->_process($self->{rules}, \%hash);
97 2         3 return %{$after};
  2         14  
98             }
99             else {
100 0         0 croak 'convert require HASH or HASH ref'
101             }
102             }
103              
104             sub _process {
105 35     35   55 my ($self, $rules, $before) = @_;
106              
107 35         65 my %after;
108 35         109 for my $name (sort keys %$rules) {
109 46         90 my $rule = $rules->{$name};
110              
111 46 100       176 if (exists $rule->{via}) {
    100          
    100          
    50          
112 6         30 $self->via($name, $rule, $before, \%after);
113             }
114             elsif (exists $rule->{from}) {
115 31         100 $self->from($name, $rule, $before, \%after);
116             }
117             elsif (exists $rule->{contain}) {
118 8         35 $self->contain($name, $rule, $before, \%after);
119             }
120             elsif (exists $rule->{define}) {
121 1         9 $self->define($name, $rule, $before, \%after);
122             }
123             else {
124             # not do this
125             }
126             }
127 35         126 return \%after;
128             }
129              
130             sub _is_all_exists {
131 37     37   68 my ($self, $before, $names) = @_;
132              
133 37         901 my $exists_size = grep { $self->_resolve_exists($before, $_) } @$names;
  40         702  
134 37 100       105 if ($exists_size == scalar @$names) {
135 22         68 return 1;
136             }
137 15         77 return 0;
138             }
139              
140             sub from {
141 31     31 1 72 my ($self, $name, $rule, $before, $after) = @_;
142              
143 31 100       108 if ($self->_is_all_exists($before, $rule->{from})) {
    100          
144 17         719 $after->{$name} = $self->_resolve_value($before, $rule->{from}->[0]);
145             } elsif (exists $rule->{default}) {
146 7         21 $after->{$name} = $self->default($rule->{default});
147             }
148             }
149              
150             sub via {
151 6     6 1 19 my ($self, $name, $rule, $before, $after) = @_;
152              
153 6 100       31 if ($self->_is_all_exists($before, $rule->{from})) {
    50          
154 5         12 my @args = map { $self->_resolve_value($before, $_) } @{$rule->{from}};
  8         550  
  5         12  
155 5         24 $after->{$name} = $rule->{via}->(@args);
156             } elsif (exists $rule->{default}) {
157 1         4 $after->{$name} = $self->default($rule->{default});
158             }
159             }
160              
161             sub define {
162 1     1 0 9 my ($self, $name, $rule, $before, $after) = @_;
163 1         5 $after->{$name} = $rule->{define};
164             }
165              
166             sub contain {
167 8     8 1 14 my ($self, $name, $rule, $before, $after) = @_;
168              
169 8         31 my $value = $self->_process($rule->{contain}, $before);
170 8 100       24 if (not %$value) {
171 3 100       13 if (exists $rule->{default}) {
172 2         7 $after->{$name} = $self->default($rule->{default});
173             }
174             else {
175             # nop
176             }
177             }
178             else {
179 5         18 $after->{$name} = $value;
180             }
181             }
182              
183             sub default {
184 10     10 1 15 my ($self, $default) = @_;
185              
186 10 100       1219 if (ref $default eq 'CODE') {
187 1         4 return $default->();
188             }
189 9         40 return $default;
190             }
191              
192             sub _resolve_value {
193 25     25   111 my ($self, $before, $name) = @_;
194              
195 25         69 my @struct = split /\./, $name;
196 25         38 my $value = $before;
197 25         47 for my $point (@struct) {
198 32         78 $value = $value->{$point};
199             }
200 25         112 return $value;
201             }
202              
203             sub _resolve_exists {
204 40     40   1219 my ($self, $before, $name) = @_;
205              
206 40         1201 my $is_exists = 0;
207 40         143 my @struct = split /\./, $name;
208 40         71 my $value = $before;
209 40         143 for my $point (@struct) {
210 47         671 $is_exists = exists $value->{$point};
211 47         114 $value = $value->{$point};
212             }
213 40         152 return $is_exists;
214             }
215              
216             1;
217             __END__