File Coverage

blib/lib/Mojo/Parameters.pm
Criterion Covered Total %
statement 107 107 100.0
branch 51 52 98.0
condition 11 14 78.5
subroutine 18 18 100.0
pod 12 12 100.0
total 199 203 98.0


line stmt bran cond sub pod time code
1             package Mojo::Parameters;
2 67     67   91782 use Mojo::Base -base;
  67         185  
  67         511  
3 67     67   1159 use overload '@{}' => sub { shift->pairs }, bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
  67     2481   1472  
  67     4193   763  
  2     1108   28  
  7847         22442  
  68         24524  
4              
5 67     67   9621 use Mojo::Util qw(decode encode url_escape url_unescape);
  67         171  
  67         165618  
6              
7             has charset => 'UTF-8';
8              
9             sub append {
10 459     459 1 946 my $self = shift;
11              
12 459         1190 my $old = $self->pairs;
13 459 100       2028 my @new = @_ == 1 ? @{shift->pairs} : @_;
  314         830  
14 459         2141 while (my ($name, $value) = splice @new, 0, 2) {
15              
16             # Multiple values
17 334 100 50     1325 if (ref $value eq 'ARRAY') { push @$old, $name => $_ // '' for @$value }
  48 100       424  
18              
19             # Single value
20 284         1230 elsif (defined $value) { push @$old, $name => $value }
21             }
22              
23 459         1683 return $self;
24             }
25              
26             sub clone {
27 1669     1669 1 3399 my $self = shift;
28              
29 1669         4265 my $clone = $self->new;
30 1669 100       5672 if (exists $self->{charset}) { $clone->{charset} = $self->{charset} }
  470         1576  
31 1669 100       4814 if (defined $self->{string}) { $clone->{string} = $self->{string} }
  192         678  
32 1477         2473 else { $clone->{pairs} = [@{$self->pairs}] }
  1477         4268  
33              
34 1669         6575 return $clone;
35             }
36              
37             sub every_param {
38 3146     3146 1 6152 my ($self, $name) = @_;
39              
40 3146         4925 my @values;
41 3146         6424 my $pairs = $self->pairs;
42 3146         8854 for (my $i = 0; $i < @$pairs; $i += 2) {
43 1171 100       4333 push @values, $pairs->[$i + 1] if $pairs->[$i] eq $name;
44             }
45              
46 3146         14432 return \@values;
47             }
48              
49             sub merge {
50 25     25 1 63 my $self = shift;
51              
52 25 100       117 my $merge = @_ == 1 ? shift->to_hash : {@_};
53 25         105 for my $name (sort keys %$merge) {
54 30         121 my $value = $merge->{$name};
55 30 100       168 defined $value ? $self->param($name => $value) : $self->remove($name);
56             }
57              
58 25         138 return $self;
59             }
60              
61 169     169 1 336 sub names { [sort keys %{shift->to_hash}] }
  169         791  
62              
63 5206 100   5206 1 318199 sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
64              
65             sub pairs {
66 8385     8385 1 13602 my $self = shift;
67              
68             # Replace parameters
69 8385 100       17751 if (@_) {
70 5         19 $self->{pairs} = shift;
71 5         13 delete $self->{string};
72 5         23 return $self;
73             }
74              
75             # Parse string
76 8380 100       21751 if (defined(my $str = delete $self->{string})) {
77 282         895 my $pairs = $self->{pairs} = [];
78 282 100       880 return $pairs unless length $str;
79              
80 281         932 my $charset = $self->charset;
81 281         1503 for my $pair (split /&/, $str) {
82 439 50       3238 next unless $pair =~ /^([^=]+)(?:=(.*))?$/;
83 439   100     2672 my ($name, $value) = ($1, $2 // '');
84              
85             # Replace "+" with whitespace, unescape and decode
86 439         1624 s/\+/ /g for $name, $value;
87 439         1412 $name = url_unescape $name;
88 439 100 66     1784 $name = decode($charset, $name) // $name if $charset;
89 439         1206 $value = url_unescape $value;
90 439 100 66     1922 $value = decode($charset, $value) // $value if $charset;
91              
92 439         1709 push @$pairs, $name, $value;
93             }
94             }
95              
96 8379   100     32133 return $self->{pairs} //= [];
97             }
98              
99             sub param {
100 2870     2870 1 6372 my ($self, $name) = (shift, shift);
101 2870 100       9657 return $self->every_param($name)->[-1] unless @_;
102 35         152 $self->remove($name);
103 35 100       199 return $self->append($name => ref $_[0] eq 'ARRAY' ? $_[0] : [@_]);
104             }
105              
106             sub parse {
107 402     402 1 932 my $self = shift;
108              
109             # Pairs
110 402 100       1505 return $self->append(@_) if @_ > 1;
111              
112             # String
113 353   100     1831 $self->{string} = shift // '';
114 353         1095 return $self;
115             }
116              
117             sub remove {
118 42     42 1 124 my ($self, $name) = @_;
119 42         126 my $pairs = $self->pairs;
120 42         84 my $i = 0;
121 42 100       305 $pairs->[$i] eq $name ? splice @$pairs, $i, 2 : ($i += 2) while $i < @$pairs;
122 42         97 return $self;
123             }
124              
125             sub to_hash {
126 363     363 1 770 my $self = shift;
127              
128 363         674 my %hash;
129 363         960 my $pairs = $self->pairs;
130 363         1362 for (my $i = 0; $i < @$pairs; $i += 2) {
131 211         467 my ($name, $value) = @{$pairs}[$i, $i + 1];
  211         649  
132              
133             # Array
134 211 100       508 if (exists $hash{$name}) {
135 37 100       233 $hash{$name} = [$hash{$name}] if ref $hash{$name} ne 'ARRAY';
136 37         72 push @{$hash{$name}}, $value;
  37         163  
137             }
138              
139             # String
140 174         796 else { $hash{$name} = $value }
141             }
142              
143 363         2786 return \%hash;
144             }
145              
146             sub to_string {
147 2805     2805 1 5264 my $self = shift;
148              
149             # String (RFC 3986)
150 2805         8440 my $charset = $self->charset;
151 2805 100       10057 if (defined(my $str = $self->{string})) {
152 239 100       1483 $str = encode $charset, $str if $charset;
153 239         3337 return url_escape $str, '^A-Za-z0-9\-._~%!$&\'()*+,;=:@/?';
154             }
155              
156             # Build pairs (HTML Living Standard)
157 2566         11825 my $pairs = $self->pairs;
158 2566 100       10066 return '' unless @$pairs;
159 104         214 my @pairs;
160 104         425 for (my $i = 0; $i < @$pairs; $i += 2) {
161 216         395 my ($name, $value) = @{$pairs}[$i, $i + 1];
  216         630  
162              
163             # Escape and replace whitespace with "+"
164 216 100       870 $name = encode $charset, $name if $charset;
165 216         12434 $name = url_escape $name, '^*\-.0-9A-Z_a-z';
166 216 100       779 $value = encode $charset, $value if $charset;
167 216         573 $value = url_escape $value, '^*\-.0-9A-Z_a-z';
168 216         834 s/\%20/\+/g for $name, $value;
169              
170 216         929 push @pairs, "$name=$value";
171             }
172              
173 104         850 return join '&', @pairs;
174             }
175              
176             1;
177              
178             =encoding utf8
179              
180             =head1 NAME
181              
182             Mojo::Parameters - Parameters
183              
184             =head1 SYNOPSIS
185              
186             use Mojo::Parameters;
187              
188             # Parse
189             my $params = Mojo::Parameters->new('foo=bar&baz=23');
190             say $params->param('baz');
191              
192             # Build
193             my $params = Mojo::Parameters->new(foo => 'bar', baz => 23);
194             push @$params, i => '♥ mojolicious';
195             say "$params";
196              
197             =head1 DESCRIPTION
198              
199             L is a container for form parameters used by L, based on L
200             3986|https://tools.ietf.org/html/rfc3986> and the L.
201              
202             =head1 ATTRIBUTES
203              
204             L implements the following attributes.
205              
206             =head2 charset
207              
208             my $charset = $params->charset;
209             $params = $params->charset('UTF-8');
210              
211             Charset used for encoding and decoding parameters, defaults to C.
212              
213             # Disable encoding and decoding
214             $params->charset(undef);
215              
216             =head1 METHODS
217              
218             L inherits all methods from L and implements the following new ones.
219              
220             =head2 append
221              
222             $params = $params->append(foo => 'ba&r');
223             $params = $params->append(foo => ['ba&r', 'baz']);
224             $params = $params->append(foo => ['bar', 'baz'], bar => 23);
225             $params = $params->append(Mojo::Parameters->new);
226              
227             Append parameters. Note that this method will normalize the parameters.
228              
229             # "foo=bar&foo=baz"
230             Mojo::Parameters->new('foo=bar')->append(Mojo::Parameters->new('foo=baz'));
231              
232             # "foo=bar&foo=baz"
233             Mojo::Parameters->new('foo=bar')->append(foo => 'baz');
234              
235             # "foo=bar&foo=baz&foo=yada"
236             Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada']);
237              
238             # "foo=bar&foo=baz&foo=yada&bar=23"
239             Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada'], bar => 23);
240              
241             =head2 clone
242              
243             my $params2 = $params->clone;
244              
245             Return a new L object cloned from these parameters.
246              
247             =head2 every_param
248              
249             my $values = $params->every_param('foo');
250              
251             Similar to L, but returns all values sharing the same name as an array reference. Note that this method will
252             normalize the parameters.
253              
254             # Get first value
255             say $params->every_param('foo')->[0];
256              
257             =head2 merge
258              
259             $params = $params->merge(foo => 'ba&r');
260             $params = $params->merge(foo => ['ba&r', 'baz']);
261             $params = $params->merge(foo => ['bar', 'baz'], bar => 23);
262             $params = $params->merge(Mojo::Parameters->new);
263              
264             Merge parameters. Note that this method will normalize the parameters.
265              
266             # "foo=baz"
267             Mojo::Parameters->new('foo=bar')->merge(Mojo::Parameters->new('foo=baz'));
268              
269             # "yada=yada&foo=baz"
270             Mojo::Parameters->new('foo=bar&yada=yada')->merge(foo => 'baz');
271              
272             # "yada=yada"
273             Mojo::Parameters->new('foo=bar&yada=yada')->merge(foo => undef);
274              
275             =head2 names
276              
277             my $names = $params->names;
278              
279             Return an array reference with all parameter names.
280              
281             # Names of all parameters
282             say for @{$params->names};
283              
284             =head2 new
285              
286             my $params = Mojo::Parameters->new;
287             my $params = Mojo::Parameters->new('foo=b%3Bar&baz=23');
288             my $params = Mojo::Parameters->new(foo => 'b&ar');
289             my $params = Mojo::Parameters->new(foo => ['ba&r', 'baz']);
290             my $params = Mojo::Parameters->new(foo => ['bar', 'baz'], bar => 23);
291              
292             Construct a new L object and L parameters if necessary.
293              
294             =head2 pairs
295              
296             my $array = $params->pairs;
297             $params = $params->pairs([foo => 'b&ar', baz => 23]);
298              
299             Parsed parameter pairs. Note that this method will normalize the parameters.
300              
301             # Remove all parameters
302             $params->pairs([]);
303              
304             =head2 param
305              
306             my $value = $params->param('foo');
307             $params = $params->param(foo => 'ba&r');
308             $params = $params->param(foo => qw(ba&r baz));
309             $params = $params->param(foo => ['ba;r', 'baz']);
310              
311             Access parameter values. If there are multiple values sharing the same name, and you want to access more than just the
312             last one, you can use L. Note that this method will normalize the parameters.
313              
314             =head2 parse
315              
316             $params = $params->parse('foo=b%3Bar&baz=23');
317              
318             Parse parameters.
319              
320             =head2 remove
321              
322             $params = $params->remove('foo');
323              
324             Remove parameters. Note that this method will normalize the parameters.
325              
326             # "bar=yada"
327             Mojo::Parameters->new('foo=bar&foo=baz&bar=yada')->remove('foo');
328              
329             =head2 to_hash
330              
331             my $hash = $params->to_hash;
332              
333             Turn parameters into a hash reference. Note that this method will normalize the parameters.
334              
335             # "baz"
336             Mojo::Parameters->new('foo=bar&foo=baz')->to_hash->{foo}[1];
337              
338             =head2 to_string
339              
340             my $str = $params->to_string;
341              
342             Turn parameters into a string.
343              
344             # "foo=bar&baz=23"
345             Mojo::Parameters->new->pairs([foo => 'bar', baz => 23])->to_string;
346              
347             =head1 OPERATORS
348              
349             L overloads the following operators.
350              
351             =head2 array
352              
353             my @pairs = @$params;
354              
355             Alias for L. Note that this will normalize the parameters.
356              
357             say $params->[0];
358             say for @$params;
359              
360             =head2 bool
361              
362             my $bool = !!$params;
363              
364             Always true.
365              
366             =head2 stringify
367              
368             my $str = "$params";
369              
370             Alias for L.
371              
372             =head1 SEE ALSO
373              
374             L, L, L.
375              
376             =cut