File Coverage

lib/URI/Template/Restrict/Expansion.pm
Criterion Covered Total %
statement 39 39 100.0
branch 8 10 80.0
condition 1 3 33.3
subroutine 10 10 100.0
pod 4 4 100.0
total 62 66 93.9


line stmt bran cond sub pod time code
1             package URI::Template::Restrict::Expansion;
2              
3 5     5   26 use strict;
  5         10  
  5         160  
4 5     5   25 use warnings;
  5         10  
  5         134  
5 5     5   25 use base 'Class::Accessor::Fast';
  5         10  
  5         1201  
6 5     5   3867 use Carp qw(croak);
  5         10  
  5         238  
7 5     5   1063 use URI::Escape qw(uri_unescape);
  5         1778  
  5         345  
8              
9             __PACKAGE__->mk_accessors(qw'op arg vars');
10              
11             {
12             package # hide from PAUSE
13             URI::Template::Restrict::Expansion::var;
14 5     5   28 use base 'Class::Accessor::Fast';
  5         9  
  5         2451  
15             __PACKAGE__->mk_accessors(qw'name default');
16             }
17              
18             my (%RE, %PATTERN, %PROCESSOR, %EXTRACTOR);
19              
20             # ----------------------------------------------------------------------
21             # Draft 03 - 4.2. Template Expansions
22             # ----------------------------------------------------------------------
23             # op = 1*ALPHA
24             # arg = *(reserved / unreserved / pct-encoded)
25             # var = varname [ "=" vardefault ]
26             # vars = var [ *("," var) ]
27             # varname = (ALPHA / DIGIT)*(ALPHA / DIGIT / "." / "_" / "-" )
28             # vardefault = *(unreserved / pct-encoded)
29             # operator = "-" op "|" arg "|" vars
30             # expansion = "{" ( var / operator ) "}"
31             # ----------------------------------------------------------------------
32             # RFC 3986 - 2. Characters
33             # ----------------------------------------------------------------------
34             # pct-encoded = "%" HEXDIG HEXDIG
35             # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
36             # reserved = gen-delims / sub-delims
37             # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
38             # sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
39             # / "*" / "+" / "," / ";" / "="
40             # ----------------------------------------------------------------------
41             {
42             $RE{op} = '[a-zA-Z]+';
43             $RE{arg} = '.*?';
44             $RE{varname} = '[a-zA-Z0-9][a-zA-Z0-9._\-]*';
45             $RE{vardefault} = '(?:[a-zA-Z0-9\-._~]|(?:%[a-fA-F0-9]{2}))*';
46             $RE{varextract} = sub {
47             my %ex = map { $_ => undef } @_;
48             my $re = join '' =>
49             grep { !exists $ex{$_} }
50             ('!', '$', '&', q|'|, '(', ')', '*', '+', ',', ';', '=', ':', '@');
51             return '(?:[a-zA-Z0-9\-._~]|[' . $re . ']|(?:%[a-fA-F0-9]{2}))*';
52             };
53             $RE{var} = "$RE{varname}(?:=$RE{vardefault})?";
54             $RE{vars} = "$RE{var}(?:,$RE{var})*";
55             }
56              
57             sub new {
58 102     102 1 238 my ($class, $expansion) = @_;
59 102         130 my ($op, $arg, $vars);
60              
61 102 100       1718 if ($expansion =~ /^($RE{var})$/) {
    50          
62             # var = varname [ "=" vardefault ]
63 43         102 ($op, $vars) = ('fill', $1);
64             }
65             elsif ($expansion =~ /^\-($RE{op})\|($RE{arg})\|($RE{vars})$/) {
66             # operator = "-" op "|" arg "|" vars
67 59         215 ($op, $arg, $vars) = ($1, $2, $3);
68             }
69              
70             # no vars
71 102 50 33     513 croak "unparsable expansion: $expansion"
72             unless defined $op and defined $vars;
73              
74 102         329 my @vars = split /,/, $vars;
75 102         192 for my $var (@vars) {
76 141         647 my ($name, $default) = split /=/, $var;
77             # replace var
78 141         878 $var = URI::Template::Restrict::Expansion::var->new({
79             name => $name,
80             default => $default
81             });
82             }
83              
84 102 100       1833 my $self = {
85             op => $op,
86             arg => $arg,
87             vars => @vars == 1 ? $vars[0] : \@vars,
88             };
89 102         607 return bless $self, $class;
90             }
91              
92             %PATTERN = (
93             fill => $RE{varextract}->(),
94             prefix => sub {
95             my $arg = shift->arg;
96             my $re = $RE{varextract}->($arg);
97             $arg = quotemeta $arg;
98             return "(?:${arg}$re)*";
99             },
100             suffix => sub {
101             my $arg = shift->arg;
102             my $re = $RE{varextract}->($arg);
103             $arg = quotemeta $arg;
104             return "(?:$re${arg})*";
105             },
106             list => sub {
107             my $arg = shift->arg;
108             my $re = $RE{varextract}->($arg);
109             $arg = quotemeta $arg;
110             return "(?:$re(?:${arg}$re)*)*";
111             },
112             join => sub {
113             my $self = shift;
114             my $arg = quotemeta $self->arg;
115             my @vars = ref $self->vars eq 'ARRAY' ? @{ $self->vars } : ($self->vars);
116             my @pattern;
117             my $re = $RE{varextract}->($self->arg, '=');
118             my $names = join('|', map { $_->name } @vars);
119             my $n = $#vars;
120             return "(?:(?:(?:${names})=$re){0,1}(?:${arg}(?:${names})=$re){0,${n}})";
121             },
122             );
123              
124             sub pattern {
125 52     52 1 61 my $self = shift;
126 52         161 my $pattern = $PATTERN{$self->op};
127 52 100       381 return ref $pattern ? $pattern->($self) : $pattern;
128             }
129              
130             %PROCESSOR = (
131             fill => sub {
132             my ($self, $vars) = @_;
133             my $var = $self->vars;
134             my $name = $var->name;
135             my $value = defined $var->default ? $var->default : '';
136             return defined $vars->{$name} ? $vars->{$name} : $value;
137             },
138             prefix => sub {
139             my ($self, $vars) = @_;
140             my $args = $vars->{$self->vars->name};
141             return '' unless defined $args;
142             my $arg = defined $self->arg ? $self->arg : '';
143             return join '', map { "${arg}${_}" } ref $args ? @$args : ($args);
144             },
145             suffix => sub {
146             my ($self, $vars) = @_;
147             my $args = $vars->{$self->vars->name};
148             return '' unless defined $args;
149             my $arg = defined $self->arg ? $self->arg : '';
150             return join '', map { "${_}${arg}" } ref $args ? @$args : ($args);
151             },
152             list => sub {
153             my ($self, $vars) = @_;
154             my $args = $vars->{$self->vars->name};
155             return '' unless defined $args and ref $args eq 'ARRAY' and @$args > 0;
156             return join defined $self->arg ? $self->arg : '', @$args;
157             },
158             join => sub {
159             my ($self, $vars) = @_;
160             my @vars = ref $self->vars eq 'ARRAY' ? @{ $self->vars } : ($self->vars);
161             my @pairs;
162             for my $var (@vars) {
163             my $name = $var->name;
164             my $value = exists $vars->{$name} ? $vars->{$name} : $var->default;
165             next unless defined $value;
166             push @pairs, "${name}=${value}";
167             }
168             return join defined $self->arg ? $self->arg : '', @pairs;
169             },
170             );
171              
172             sub process {
173 69     69 1 106 my ($self, $vars) = @_;
174 69         213 my $processor = $PROCESSOR{$self->op};
175 69         450 return $processor->($self, $vars);
176             }
177              
178             %EXTRACTOR = (
179             fill => sub {
180             my ($self, $var) = @_;
181             my $value = $var eq '' ? undef : uri_unescape($var);
182             return ($self->vars->name, $value);
183             },
184             prefix => sub {
185             my ($self, $var) = @_;
186             my $arg = $self->arg;
187             $var =~ s/^$arg//;
188             my @vars = map { uri_unescape($_) } split /$arg/, $var;
189             return ($self->vars->name, @vars > 1 ? \@vars : @vars ? $vars[0] : undef);
190             },
191             suffix => sub {
192             my ($self, $var) = @_;
193             my $arg = $self->arg;
194             $var =~ s/$arg$//;
195             my @vars = map { uri_unescape($_) } split /$arg/, $var;
196             return ($self->vars->name, @vars > 1 ? \@vars : @vars ? $vars[0] : undef);
197             },
198             list => sub {
199             my ($self, $var) = @_;
200             my $arg = $self->arg;
201             my @vars = map { uri_unescape($_) } split /$arg/, $var;
202             return ($self->vars->name, @vars > 0 ? \@vars : undef);
203             },
204             join => sub {
205             my ($self, $var) = @_;
206             my %vars =
207             map { ($_->name, $_->default) }
208             ref $self->vars eq 'ARRAY' ? @{ $self->vars } : ($self->vars);
209             my $arg = $self->arg;
210             for my $pair (split /$arg/, $var) {
211             my ($name, $value) = split /=/, $pair;
212             $vars{$name} = uri_unescape($value);
213             }
214             return %vars;
215             },
216             );
217              
218             sub extract {
219 52     52 1 101 my ($self, $var) = @_;
220 52         114 my $extractor = $EXTRACTOR{$self->op};
221 52         279 return $extractor->($self, $var);
222             }
223              
224             1;
225              
226             =head1 NAME
227              
228             URI::Template::Restrict::Expansion - Template expansions
229              
230             =head1 METHODS
231              
232             =head2 process
233              
234             =head2 extract
235              
236             =head1 PROPERTIES
237              
238             =head2 op
239              
240             =head2 arg
241              
242             =head2 vars
243              
244             =head2 pattern
245              
246             =head1 AUTHOR
247              
248             NAKAGAWA Masaki Emasaki@cpan.orgE
249              
250             =head1 LICENSE
251              
252             This library is free software; you can redistribute it and/or modify
253             it under the same terms as Perl itself.
254              
255             =head1 SEE ALSO
256              
257             L
258              
259             =cut