File Coverage

blib/lib/Form/Tiny/Utils.pm
Criterion Covered Total %
statement 94 98 95.9
branch 26 30 86.6
condition 11 15 73.3
subroutine 16 17 94.1
pod 0 8 0.0
total 147 168 87.5


line stmt bran cond sub pod time code
1             package Form::Tiny::Utils;
2             $Form::Tiny::Utils::VERSION = '2.20';
3 52     52   1207 use v5.10;
  52         196  
4 52     52   299 use strict;
  52         142  
  52         1175  
5 52     52   247 use warnings;
  52         113  
  52         1576  
6 52     52   348 use Exporter qw(import);
  52         150  
  52         1942  
7 52     52   342 use Carp qw(croak);
  52         163  
  52         3236  
8 52     52   373 use Scalar::Util qw(blessed);
  52         119  
  52         67673  
9              
10             our @EXPORT;
11             our @EXPORT_OK = qw(
12             try
13             trim
14             uniq
15             create_anon_form_meta
16             create_form_meta
17             get_package_form_meta
18             set_form_meta_class
19             has_form_meta
20             );
21              
22             our %EXPORT_TAGS = (
23             meta_handlers => [
24             qw(
25             create_anon_form_meta
26             create_form_meta
27             get_package_form_meta
28             set_form_meta_class
29             has_form_meta
30             )
31             ],
32             );
33              
34             sub try
35             {
36 102     102 0 9493 my ($sub) = @_;
37              
38 102         162 local $@;
39 102         175 my $ret = not eval {
40 102         256 $sub->();
41 93         12540 return 1;
42             };
43              
44 102 100 66     449 if ($@ && $ret) {
45 9         20 $ret = $@;
46             }
47              
48 102         341 return $ret;
49             }
50              
51             sub trim
52             {
53 23     23 0 54 my ($value) = @_;
54 23         99 $value =~ s/\A\s+//;
55 23         70 $value =~ s/\s+\z//;
56              
57 23         89 return $value;
58             }
59              
60             sub uniq
61             {
62 5     5 0 11 my %seen;
63 5         38 return grep { !$seen{$_}++ } @_;
  8         124  
64             }
65              
66             # FORM METADATA
67             my $meta_class = 'Form::Tiny::Meta';
68             my %meta;
69              
70             sub create_anon_form_meta
71             {
72 120     120 0 295 my (@roles) = @_;
73 120         24326 require Form::Tiny::Meta;
74 120         2451 my $meta = $meta_class->new;
75 120         4859 $meta->set_meta_roles([@roles]);
76              
77 120         3466 return $meta;
78             }
79              
80             sub create_form_meta
81             {
82 66     66 0 209 my ($package, @roles) = @_;
83              
84             croak "form meta for $package already exists"
85 66 50       256 if exists $meta{$package};
86              
87 66         220 $meta{$package} = create_anon_form_meta(@roles);
88 66         321 $meta{$package}->set_package($package);
89              
90 66         459 return $meta{$package};
91             }
92              
93             sub has_form_meta
94             {
95 191   66 191 0 2243 return exists $meta{ref $_[0] || $_[0]}
96             || blessed $_[0] && $_[0]->DOES('Form::Tiny::Form');
97             }
98              
99             sub get_package_form_meta
100             {
101 927   66 927 0 3225 my $package_name = ref $_[0] || $_[0];
102 927         1713 my $form_meta = $meta{$package_name};
103              
104 927 100 66     5052 if (!$form_meta || !$form_meta->complete) {
105             croak "no form meta declared for $package_name"
106 65 50       248 unless exists $meta{$package_name};
107              
108 65 100       252 croak "Form $package_name seems to be empty. "
109             . 'Please implement the form or call __PACKAGE__->form_meta explicitly. '
110             . 'See Form::Tiny::Manual::Cookbook "Empty forms" section for details'
111             if ref $_[0];
112              
113 63         268 $form_meta->bootstrap;
114             }
115              
116 925         3725 return $form_meta;
117             }
118              
119             sub set_form_meta_class
120             {
121 0     0 0 0 my ($class) = @_;
122              
123 0 0       0 croak 'form meta class must extend Form::Tiny::Meta'
124             unless $class->DOES('Form::Tiny::Meta');
125              
126 0         0 $meta_class = $class;
127 0         0 return;
128             }
129              
130             # internal use functions (not exported)
131              
132             sub _find_field
133             {
134 1379     1379   2216 my ($fields, $field_def) = @_;
135              
136 1379         1751 my @path = @{$field_def->get_name_path->path};
  1379         23459  
137 1379         11813 my @arrays = map { $_ eq 'ARRAY' } @{$field_def->get_name_path->meta};
  2462         13309  
  1379         20538  
138              
139             # the result goes here
140 1379         2334 my @found;
141             my $traverser;
142             $traverser = sub {
143 1771     1771   2979 my ($curr_path, $index, $value) = @_;
144              
145 1771 100       3215 if ($index == @path) {
146              
147             # we reached the end of the tree
148 162         399 push @found, [$curr_path, $value];
149             }
150             else {
151 1609         3005 my $current_ref = ref $value;
152              
153 1609 100       2615 if ($arrays[$index]) {
154              
155             # It's an array, make sure the actual ref type does not mismatch the spec
156 112 100       250 return unless $current_ref eq 'ARRAY';
157              
158 98 100       206 if (@$value == 0) {
159              
160             # we wanted to have a deeper structure, but its not there, so clearly an error
161 10 100       43 return unless $index == $#path;
162              
163             # we had aref here, so we want it back in resulting hash
164 8         22 push @found, [$curr_path, [], 1];
165             }
166             else {
167 88         206 for my $ind (0 .. $#$value) {
168             return # may be an error, exit early
169 155 100       539 unless $traverser->([@$curr_path, $ind], $index + 1, $value->[$ind]);
170             }
171             }
172             }
173              
174             else {
175             # it's not the leaf of the tree yet, so we require a hash
176 1497         2088 my $next = $path[$index];
177 1497 100 100     6372 return unless $current_ref eq 'HASH' && exists $value->{$next};
178 237         1250 return $traverser->([@$curr_path, $next], $index + 1, $value->{$next});
179             }
180             }
181              
182 240         729 return 1; # all ok
183 1379         5137 };
184              
185             # manually free traverser after it's done (memroy leak)
186 1379         2863 my $result = $traverser->([], 0, $fields);
187 1379         6617 $traverser = undef;
188              
189 1379 100       2584 if ($result) {
190             return [
191             map {
192 103         220 {
193 160         785 path => $_->[0],
194             value => $_->[1],
195             structure => $_->[2]
196             }
197             } @found
198             ];
199             }
200              
201 1276         3000 return;
202             }
203              
204             sub _assign_field
205             {
206 289     289   717 my ($fields, $field_def, $path_values) = @_;
207              
208 289         406 my @arrays = map { $_ eq 'ARRAY' } @{$field_def->get_name_path->meta};
  499         3244  
  289         5027  
209 289         691 for my $path_value (@$path_values) {
210 346         473 my @parts = @{$path_value->{path}};
  346         1164  
211 346         607 my $current = \$fields;
212              
213 346         821 for my $i (0 .. $#parts) {
214              
215             # array_path will contain array indexes for each array marker
216 670 100       1190 if ($arrays[$i]) {
217 183         243 $current = \${$current}->[$parts[$i]];
  183         361  
218             }
219             else {
220 487         608 $current = \${$current}->{$parts[$i]};
  487         1355  
221             }
222             }
223              
224 346         1277 $$current = $path_value->{value};
225             }
226             }
227              
228             1;
229