File Coverage

blib/lib/Form/Tiny/Path.pm
Criterion Covered Total %
statement 87 89 97.7
branch 22 26 84.6
condition 6 9 66.6
subroutine 14 15 93.3
pod 0 9 0.0
total 129 148 87.1


line stmt bran cond sub pod time code
1             package Form::Tiny::Path;
2             $Form::Tiny::Path::VERSION = '2.20';
3 52     52   70930 use v5.10;
  52         195  
4 52     52   316 use strict;
  52         115  
  52         1248  
5 52     52   314 use warnings;
  52         121  
  52         1400  
6 52     52   810 use Moo;
  52         11860  
  52         434  
7 52     52   20042 use Carp qw(croak);
  52         144  
  52         2427  
8 52     52   903 use Types::Standard qw(ArrayRef);
  52         121123  
  52         344  
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             sub BUILD
32             {
33 306     306 0 119334 my ($self) = @_;
34              
35 306         502 my @parts = @{$self->path};
  306         998  
36              
37             # we allow empty paths here due to ->empty and ->clone methods
38             # we disallow them in ->from_name instead
39 306 50       960 if (scalar @parts) {
40             croak 'path specified contained an empty part: ' . $self->dump
41 306 100       549 if scalar grep { length $_ eq 0 } @parts;
  451         1411  
42              
43 304 100       7208 croak 'path specified started with an array: ' . $self->dump
44             if $self->meta->[0] eq 'ARRAY';
45             }
46             }
47              
48             sub dump
49             {
50 14     14 0 7264 my ($self) = @_;
51 14         23 my @parts = @{$self->path};
  14         42  
52 14         23 my @meta = @{$self->meta};
  14         32  
53              
54             return join ' -> ',
55 14         31 map { "`$parts[$_]` ($meta[$_])" }
  29         672  
56             0 .. $#parts;
57             }
58              
59             sub from_name
60             {
61 304     304 0 799 my ($self, $name) = @_;
62              
63 304 100       979 croak 'path specified was empty'
64             unless length $name;
65              
66             # use custom escape character for path building
67             # (won't be mistaken for literal backslash)
68 303         533 my $escape = "\x00";
69 303 100       2413 $name =~ s/(\Q$escape_character\E{1,2})/length $1 == 2 ? $escape_character : $escape/ge;
  46         231  
70              
71 303         2060 my @parts = split /(?
72 303         554 my @meta;
73              
74 303         643 for my $part (@parts) {
75 447 100       876 if ($part eq $array_marker) {
76 53         114 push @meta, 'ARRAY';
77             }
78             else {
79 394         875 push @meta, 'HASH';
80             }
81             }
82              
83             @parts = map {
84 303         596 s{ $escape ( \Q$nesting_separator\E | \Q$array_marker\E ) }{$1}gx;
  447         2291  
85 447         1432 $_
86             } @parts;
87              
88 303         5117 return $self->new(path => \@parts, meta => \@meta);
89             }
90              
91             sub empty
92             {
93 0     0 0 0 my ($self) = @_;
94 0         0 return $self->new(path => [], meta => []);
95             }
96              
97             sub clone
98             {
99 3     3 0 10 my ($self) = @_;
100 3         8 return $self->new(path => [@{$self->path}], meta => [@{$self->meta}]);
  3         13  
  3         66  
101             }
102              
103             sub append
104             {
105 3     3 0 10 my ($self, $meta, $key) = @_;
106 3 50       12 $key = $array_marker
107             if $meta eq 'ARRAY';
108              
109 3         5 push @{$self->path}, $key;
  3         12  
110 3         5 push @{$self->meta}, $meta;
  3         19  
111 3         22 return $self;
112             }
113              
114             sub make_name_path
115             {
116 15     15 0 32 my ($self, $prefix) = @_;
117              
118 15         23 my @real_path = @{$self->path};
  15         58  
119 15         43 my $meta = $self->meta;
120              
121 15 50       49 @real_path = @real_path[0 .. $prefix]
122             if defined $prefix;
123              
124 15         48 for my $ind (0 .. $#real_path) {
125 29 100       78 if ($meta->[$ind] ne 'ARRAY') {
126 28         314 $real_path[$ind] =~ s{
127             (\Q$escape_character\E | \Q$nesting_separator\E | \A\Q$array_marker\E\z)
128             }{$escape_character$1}gx;
129             }
130             }
131              
132 15         255 return @real_path;
133             }
134              
135             sub join
136             {
137 15     15 0 37 my ($self, $prefix) = @_;
138 15         44 return join $nesting_separator, $self->make_name_path($prefix);
139             }
140              
141             sub follow
142             {
143 9     9 0 96 my ($self, $structure) = @_;
144              
145 9 100       54 return undef if !ref $structure;
146              
147 7         15 my @found = ($structure);
148 7         12 my @path = @{$self->path};
  7         20  
149 7         12 my @meta = @{$self->meta};
  7         17  
150 7         22 my $has_array = 0;
151              
152 7         22 for my $ind (0 .. $#path) {
153 18         27 my $is_array = $meta[$ind] eq 'ARRAY';
154 18         22 my @new_found;
155              
156 18         27 for my $item (@found) {
157 25 100 66     98 if ($is_array && ref $item eq 'ARRAY') {
    50 33        
158 8         11 push @new_found, @{$item};
  8         16  
159             }
160             elsif (ref $item eq 'HASH' && exists $item->{$path[$ind]}) {
161 17         39 push @new_found, $item->{$path[$ind]};
162             }
163             }
164              
165 18         28 @found = @new_found;
166 18   100     57 $has_array ||= $is_array;
167             }
168              
169 7 100       48 return $has_array
170             ? \@found
171             : $found[0];
172             }
173              
174             1;
175