File Coverage

blib/lib/Form/Tiny/Path.pm
Criterion Covered Total %
statement 96 103 93.2
branch 24 28 85.7
condition 7 15 46.6
subroutine 16 17 94.1
pod 0 11 0.0
total 143 174 82.1


line stmt bran cond sub pod time code
1             package Form::Tiny::Path;
2             $Form::Tiny::Path::VERSION = '2.26';
3 54     54   88870 use v5.10;
  54         217  
4 54     54   316 use strict;
  54         108  
  54         1700  
5 54     54   258 use warnings;
  54         114  
  54         3270  
6 54     54   1032 use Moo;
  54         6522  
  54         520  
7 54     54   25680 use Carp qw(croak);
  54         138  
  54         3974  
8 54     54   813 use Types::Standard qw(ArrayRef);
  54         110404  
  54         658  
9              
10             our $nesting_separator = q{.};
11             our $array_marker = q{*};
12             our $escape_character = q{\\};
13              
14             has 'path' => (
15             is => 'ro',
16             isa => ArrayRef,
17             writer => '_set_path',
18             required => 1,
19             );
20              
21             # Note: this clashes with 'meta' from Moo :/
22             # will stay as it is though as long as it works (overrides the other meta)
23             # properly, since can't know how much external code uses it
24             has 'meta' => (
25             is => 'ro',
26             isa => ArrayRef,
27             writer => '_set_meta',
28             required => 1,
29             );
30              
31             # cache for meta array positions
32             has 'meta_arrays' => (
33             is => 'ro',
34             default => sub {
35             return [
36             map {
37             $_ eq 'ARRAY'
38             } @{$_[0]->meta}
39             ];
40             },
41             init_arg => undef,
42             );
43              
44             sub BUILD
45             {
46 364     364 0 9847 my ($self) = @_;
47              
48 364         607 my @parts = @{$self->path};
  364         1308  
49              
50             # we allow empty paths here due to ->empty and ->clone methods
51             # we disallow them in ->from_name instead
52 364 100       1503 if (scalar @parts) {
53             croak 'path specified contained an empty part: ' . $self->dump
54 324 100       678 if scalar grep { length $_ eq 0 } @parts;
  474         1701  
55              
56 322 100       9828 croak 'path specified started with an array: ' . $self->dump
57             if $self->meta_arrays->[0];
58             }
59             }
60              
61             sub dump
62             {
63 14     14 0 6850 my ($self) = @_;
64 14         18 my @parts = @{$self->path};
  14         41  
65 14         19 my @meta = @{$self->meta};
  14         32  
66              
67             return join ' -> ',
68 14         31 map { "`$parts[$_]` ($meta[$_])" }
  29         825  
69             0 .. $#parts;
70             }
71              
72             sub from_name
73             {
74 318     318 0 942 my ($self, $name) = @_;
75              
76 318 100       1245 croak 'path specified was empty'
77             unless length $name;
78              
79             # use custom escape character for path building
80             # (won't be mistaken for literal backslash)
81 317         619 my $escape = "\x00";
82 317 100       2951 $name =~ s/(\Q$escape_character\E{1,2})/length $1 == 2 ? $escape_character : $escape/ge;
  46         263  
83              
84 317         2427 my @parts = split /(?
85 317         702 my @meta;
86              
87 317         732 for my $part (@parts) {
88 465 100       1116 if ($part eq $array_marker) {
89 56         124 push @meta, 'ARRAY';
90             }
91             else {
92 409         1049 push @meta, 'HASH';
93             }
94             }
95              
96             @parts = map {
97 317         702 s{ $escape ( \Q$nesting_separator\E | \Q$array_marker\E ) }{$1}gx;
  465         3404  
98 465         1552 $_
99             } @parts;
100              
101 317         7159 return $self->new(path => \@parts, meta => \@meta);
102             }
103              
104             sub empty
105             {
106 40     40 0 82 my ($self) = @_;
107 40         914 return $self->new(path => [], meta => []);
108             }
109              
110             sub clone
111             {
112 7     7 0 22 my ($self) = @_;
113 7         15 return $self->new(path => [@{$self->path}], meta => [@{$self->meta}]);
  7         35  
  7         179  
114             }
115              
116             sub prepend
117             {
118 71     71 0 135 my ($self, $meta, $key) = @_;
119 71 100 33     185 $key //= $array_marker
120             if $meta eq 'ARRAY';
121              
122 71         94 unshift @{$self->path}, $key;
  71         166  
123 71         115 unshift @{$self->meta}, $meta;
  71         150  
124 71         139 return $self;
125             }
126              
127             sub append
128             {
129 0     0 0 0 my ($self, $meta, $key) = @_;
130 0 0 0     0 $key //= $array_marker
131             if $meta eq 'ARRAY';
132              
133 0         0 push @{$self->path}, $key;
  0         0  
134 0         0 push @{$self->meta}, $meta;
  0         0  
135 0         0 return $self;
136             }
137              
138             sub append_path
139             {
140 7     7 0 22 my ($self, $other_path) = @_;
141              
142 7         16 push @{$self->path}, @{$other_path->path};
  7         21  
  7         27  
143 7         14 push @{$self->meta}, @{$other_path->meta};
  7         21  
  7         35  
144 7         43 return $self;
145             }
146              
147             sub make_name_path
148             {
149 59     59 0 119 my ($self, $prefix) = @_;
150              
151 59         79 my @real_path = @{$self->path};
  59         178  
152 59         132 my $meta = $self->meta;
153              
154 59 50       166 @real_path = @real_path[0 .. $prefix]
155             if defined $prefix;
156              
157 59         168 for my $ind (0 .. $#real_path) {
158 111 100       314 if ($meta->[$ind] ne 'ARRAY') {
159 98         1237 $real_path[$ind] =~ s{
160             (\Q$escape_character\E | \Q$nesting_separator\E | \A\Q$array_marker\E\z)
161             }{$escape_character$1}gx;
162             }
163             }
164              
165 59         679 return @real_path;
166             }
167              
168             sub join
169             {
170 59     59 0 139 my ($self, $prefix) = @_;
171 59         162 return join $nesting_separator, $self->make_name_path($prefix);
172             }
173              
174             sub follow
175             {
176 9     9 0 128 my ($self, $structure) = @_;
177              
178 9 100       40 return undef if !ref $structure;
179              
180 7         20 my @found = ($structure);
181 7         11 my @path = @{$self->path};
  7         32  
182 7         22 my $meta = $self->meta_arrays;
183 7         13 my $has_array = 0;
184              
185 7         48 for my $ind (0 .. $#path) {
186 18         35 my $is_array = $meta->[$ind];
187 18         28 my @new_found;
188              
189 18         30 for my $item (@found) {
190 25 100 66     159 if ($is_array && ref $item eq 'ARRAY') {
    50 33        
191 8         12 push @new_found, @{$item};
  8         20  
192             }
193             elsif (ref $item eq 'HASH' && exists $item->{$path[$ind]}) {
194 17         45 push @new_found, $item->{$path[$ind]};
195             }
196             }
197              
198 18         40 @found = @new_found;
199 18   100     66 $has_array ||= $is_array;
200             }
201              
202 7 100       70 return $has_array
203             ? \@found
204             : $found[0];
205             }
206              
207             1;
208