File Coverage

blib/lib/Form/Tiny/Path.pm
Criterion Covered Total %
statement 87 96 90.6
branch 21 26 80.7
condition 6 9 66.6
subroutine 14 16 87.5
pod 0 10 0.0
total 128 157 81.5


line stmt bran cond sub pod time code
1             package Form::Tiny::Path;
2             $Form::Tiny::Path::VERSION = '2.21';
3 53     53   69104 use v5.10;
  53         206  
4 53     53   310 use strict;
  53         125  
  53         1194  
5 53     53   278 use warnings;
  53         122  
  53         1386  
6 53     53   798 use Moo;
  53         11443  
  53         431  
7 53     53   20340 use Carp qw(croak);
  53         168  
  53         2490  
8 53     53   904 use Types::Standard qw(ArrayRef);
  53         118605  
  53         336  
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 310     310 0 7001 my ($self) = @_;
47              
48 310         495 my @parts = @{$self->path};
  310         934  
49              
50             # we allow empty paths here due to ->empty and ->clone methods
51             # we disallow them in ->from_name instead
52 310 50       969 if (scalar @parts) {
53             croak 'path specified contained an empty part: ' . $self->dump
54 310 100       557 if scalar grep { length $_ eq 0 } @parts;
  458         1413  
55              
56 308 100       6848 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 7163 my ($self) = @_;
64 14         22 my @parts = @{$self->path};
  14         46  
65 14         20 my @meta = @{$self->meta};
  14         28  
66              
67             return join ' -> ',
68 14         33 map { "`$parts[$_]` ($meta[$_])" }
  29         742  
69             0 .. $#parts;
70             }
71              
72             sub from_name
73             {
74 307     307 0 775 my ($self, $name) = @_;
75              
76 307 100       915 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 306         535 my $escape = "\x00";
82 306 100       1944 $name =~ s/(\Q$escape_character\E{1,2})/length $1 == 2 ? $escape_character : $escape/ge;
  46         263  
83              
84 306         1979 my @parts = split /(?
85 306         619 my @meta;
86              
87 306         638 for my $part (@parts) {
88 452 100       904 if ($part eq $array_marker) {
89 55         115 push @meta, 'ARRAY';
90             }
91             else {
92 397         860 push @meta, 'HASH';
93             }
94             }
95              
96             @parts = map {
97 306         607 s{ $escape ( \Q$nesting_separator\E | \Q$array_marker\E ) }{$1}gx;
  452         2264  
98 452         1488 $_
99             } @parts;
100              
101 306         5107 return $self->new(path => \@parts, meta => \@meta);
102             }
103              
104             sub empty
105             {
106 0     0 0 0 my ($self) = @_;
107 0         0 return $self->new(path => [], meta => []);
108             }
109              
110             sub clone
111             {
112 4     4 0 14 my ($self) = @_;
113 4         9 return $self->new(path => [@{$self->path}], meta => [@{$self->meta}]);
  4         35  
  4         87  
114             }
115              
116             sub append
117             {
118 0     0 0 0 my ($self, $meta, $key) = @_;
119 0 0       0 $key = $array_marker
120             if $meta eq 'ARRAY';
121              
122 0         0 push @{$self->path}, $key;
  0         0  
123 0         0 push @{$self->meta}, $meta;
  0         0  
124 0         0 return $self;
125             }
126              
127             sub append_path
128             {
129 4     4 0 51 my ($self, $other_path) = @_;
130              
131 4         11 push @{$self->path}, @{$other_path->path};
  4         14  
  4         15  
132 4         9 push @{$self->meta}, @{$other_path->meta};
  4         11  
  4         12  
133 4         10 return $self;
134             }
135              
136             sub make_name_path
137             {
138 16     16 0 34 my ($self, $prefix) = @_;
139              
140 16         21 my @real_path = @{$self->path};
  16         65  
141 16         40 my $meta = $self->meta;
142              
143 16 50       43 @real_path = @real_path[0 .. $prefix]
144             if defined $prefix;
145              
146 16         52 for my $ind (0 .. $#real_path) {
147 33 100       119 if ($meta->[$ind] ne 'ARRAY') {
148 30         354 $real_path[$ind] =~ s{
149             (\Q$escape_character\E | \Q$nesting_separator\E | \A\Q$array_marker\E\z)
150             }{$escape_character$1}gx;
151             }
152             }
153              
154 16         302 return @real_path;
155             }
156              
157             sub join
158             {
159 16     16 0 40 my ($self, $prefix) = @_;
160 16         49 return join $nesting_separator, $self->make_name_path($prefix);
161             }
162              
163             sub follow
164             {
165 9     9 0 89 my ($self, $structure) = @_;
166              
167 9 100       32 return undef if !ref $structure;
168              
169 7         16 my @found = ($structure);
170 7         9 my @path = @{$self->path};
  7         21  
171 7         18 my $meta = $self->meta_arrays;
172 7         11 my $has_array = 0;
173              
174 7         21 for my $ind (0 .. $#path) {
175 18         27 my $is_array = $meta->[$ind];
176 18         23 my @new_found;
177              
178 18         29 for my $item (@found) {
179 25 100 66     105 if ($is_array && ref $item eq 'ARRAY') {
    50 33        
180 8         10 push @new_found, @{$item};
  8         13  
181             }
182             elsif (ref $item eq 'HASH' && exists $item->{$path[$ind]}) {
183 17         36 push @new_found, $item->{$path[$ind]};
184             }
185             }
186              
187 18         29 @found = @new_found;
188 18   100     65 $has_array ||= $is_array;
189             }
190              
191 7 100       56 return $has_array
192             ? \@found
193             : $found[0];
194             }
195              
196             1;
197