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.19';
3 52     52   68957 use v5.10;
  52         221  
4 52     52   368 use strict;
  52         131  
  52         1288  
5 52     52   317 use warnings;
  52         126  
  52         1380  
6 52     52   806 use Moo;
  52         12090  
  52         490  
7 52     52   21453 use Carp qw(croak);
  52         131  
  52         2458  
8 52     52   965 use Types::Standard qw(ArrayRef);
  52         117420  
  52         351  
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 119439 my ($self) = @_;
34              
35 306         561 my @parts = @{$self->path};
  306         1090  
36              
37             # we allow empty paths here due to ->empty and ->clone methods
38             # we disallow them in ->from_name instead
39 306 50       987 if (scalar @parts) {
40             croak 'path specified contained an empty part: ' . $self->dump
41 306 100       584 if scalar grep { length $_ eq 0 } @parts;
  451         1387  
42              
43 304 100       7559 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 7209 my ($self) = @_;
51 14         24 my @parts = @{$self->path};
  14         44  
52 14         22 my @meta = @{$self->meta};
  14         30  
53              
54             return join ' -> ',
55 14         35 map { "`$parts[$_]` ($meta[$_])" }
  29         670  
56             0 .. $#parts;
57             }
58              
59             sub from_name
60             {
61 304     304 0 803 my ($self, $name) = @_;
62              
63 304 100       924 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         553 my $escape = "\x00";
69 303 100       2019 $name =~ s/(\Q$escape_character\E{1,2})/length $1 == 2 ? $escape_character : $escape/ge;
  46         220  
70              
71 303         2065 my @parts = split /(?
72 303         559 my @meta;
73              
74 303         626 for my $part (@parts) {
75 447 100       904 if ($part eq $array_marker) {
76 53         119 push @meta, 'ARRAY';
77             }
78             else {
79 394         891 push @meta, 'HASH';
80             }
81             }
82              
83             @parts = map {
84 303         694 s{ $escape ( \Q$nesting_separator\E | \Q$array_marker\E ) }{$1}gx;
  447         2301  
85 447         1392 $_
86             } @parts;
87              
88 303         5150 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 8 my ($self) = @_;
100 3         9 return $self->new(path => [@{$self->path}], meta => [@{$self->meta}]);
  3         22  
  3         63  
101             }
102              
103             sub append
104             {
105 3     3 0 10 my ($self, $meta, $key) = @_;
106 3 50       10 $key = $array_marker
107             if $meta eq 'ARRAY';
108              
109 3         13 push @{$self->path}, $key;
  3         14  
110 3         6 push @{$self->meta}, $meta;
  3         19  
111 3         10 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         57  
119 15         47 my $meta = $self->meta;
120              
121 15 50       40 @real_path = @real_path[0 .. $prefix]
122             if defined $prefix;
123              
124 15         47 for my $ind (0 .. $#real_path) {
125 29 100       73 if ($meta->[$ind] ne 'ARRAY') {
126 28         336 $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         246 return @real_path;
133             }
134              
135             sub join
136             {
137 15     15 0 37 my ($self, $prefix) = @_;
138 15         42 return join $nesting_separator, $self->make_name_path($prefix);
139             }
140              
141             sub follow
142             {
143 9     9 0 97 my ($self, $structure) = @_;
144              
145 9 100       30 return undef if !ref $structure;
146              
147 7         17 my @found = ($structure);
148 7         8 my @path = @{$self->path};
  7         26  
149 7         26 my @meta = @{$self->meta};
  7         19  
150 7         16 my $has_array = 0;
151              
152 7         19 for my $ind (0 .. $#path) {
153 18         32 my $is_array = $meta[$ind] eq 'ARRAY';
154 18         22 my @new_found;
155              
156 18         25 for my $item (@found) {
157 25 100 66     104 if ($is_array && ref $item eq 'ARRAY') {
    50 33        
158 8         11 push @new_found, @{$item};
  8         19  
159             }
160             elsif (ref $item eq 'HASH' && exists $item->{$path[$ind]}) {
161 17         56 push @new_found, $item->{$path[$ind]};
162             }
163             }
164              
165 18         31 @found = @new_found;
166 18   100     73 $has_array ||= $is_array;
167             }
168              
169 7 100       72 return $has_array
170             ? \@found
171             : $found[0];
172             }
173              
174             1;
175