diff --git a/lib/Mojo/Log.pm b/lib/Mojo/Log.pm index bfedc49f7e..90d9ad0318 100644 --- a/lib/Mojo/Log.pm +++ b/lib/Mojo/Log.pm @@ -99,7 +99,7 @@ sub _default { my ($s, $m, $h, $day, $month, $year) = localtime $time; $time = sprintf '%04d-%02d-%02d %02d:%02d:%08.5f', $year + 1900, $month + 1, $day, $h, $m, "$s." . ((split /\./, $time)[1] // 0); - return "[$time] [$$] [$level] " . join(' ', @_) . "\n"; + return "[$time] [$$] [$level] " . join(' ', map {defined $_ ? $_ : 'undef'} @_) . "\n"; } sub _log { @@ -123,7 +123,7 @@ sub _message { sub _short { my ($time, $level) = (shift, shift); my ($magic, $short) = ("<$MAGIC{$level}>", substr($level, 0, 1)); - return "${magic}[$$] [$short] " . join(' ', @_) . "\n"; + return "${magic}[$$] [$short] " . join(' ', map {defined $_ ? $_ : 'undef'} @_) . "\n"; } package Mojo::Log::_Capture; diff --git a/lib/Mojo/Parameters.pm b/lib/Mojo/Parameters.pm index d5890b11ce..e4867da91f 100644 --- a/lib/Mojo/Parameters.pm +++ b/lib/Mojo/Parameters.pm @@ -143,6 +143,37 @@ sub to_hash { return \%hash; } +sub to_deep_hash { + my $self = shift; + + my %hash; + my $pairs = $self->pairs; + for (my $i = 0; $i < @$pairs; $i += 2) { + my ($name, $value) = @{$pairs}[$i, $i + 1]; + my @parts; + if ($name =~ m{^([^\[\]]+)((?:\[[^\[\]]+\])+)\z}) { + @parts = ($1); + push @parts, $2 =~ /\[([^\[\]]+)\]/g; + } + else { @parts = ($name) } + my $target = \%hash; + while (@parts > 1) { + my $part = shift @parts; + $target->{$part} = {} unless ref $target->{$part} eq 'HASH'; + $target = $target->{$part}; + } + + my $part = $parts[-1]; + if (exists $target->{$part}) { + $target->{$part} = [$target->{$part}] if ref $target->{$part} ne 'ARRAY'; + push @{$target->{$part}}, $value; + } + else { $target->{$part} = $value } + } + + return \%hash; +} + sub to_string { my $self = shift; @@ -335,6 +366,22 @@ Turn parameters into a hash reference. Note that this method will normalize the # "baz" Mojo::Parameters->new('foo=bar&foo=baz')->to_hash->{foo}[1]; +=head2 to_deep_hash + + my $hash = $params->to_deep_hash; + +Turn bracket notation into nested hash references. Parameter names without valid, non-empty bracket segments remain +unchanged, and repeated values at the same path are represented as arrays. + + # {foo => {bar => 'baz'}} + Mojo::Parameters->new('foo[bar]=baz')->to_deep_hash; + + # {foo => {bar => {baz => 'yada'}}} + Mojo::Parameters->new('foo[bar][baz]=yada')->to_deep_hash; + +Unlike L, this method interprets bracket notation. Numeric bracket segments are hash keys, not array +indexes. + =head2 to_string my $str = $params->to_string; diff --git a/t/mojo/log.t b/t/mojo/log.t index 5b10430052..d1fed02e6a 100644 --- a/t/mojo/log.t +++ b/t/mojo/log.t @@ -36,12 +36,14 @@ subtest 'Logging to STDERR' => sub { $log->fatal('I ♥ Mojolicious'); $log->debug('Works too'); $log->debug(sub { return 'And this', 'too' }); + $log->info(undef, 'works'); } my $content = decode 'UTF-8', $buffer; like $content, qr/\[.*\] \[error\] Just works\n/, 'right error message'; like $content, qr/\[.*\] \[fatal\] I ♥ Mojolicious\n/, 'right fatal message'; like $content, qr/\[.*\] \[debug\] Works too\n/, 'right debug message'; like $content, qr/\[.*\] \[debug\] And this too\n/, 'right debug message'; + like $content, qr/\[.*\] \[info\] undef works\n/, 'right debug message'; }; subtest 'Formatting' => sub { @@ -73,6 +75,7 @@ subtest 'Short log messages (systemd)' => sub { like $log->format->(time, 'error', 'Test 123'), qr/^<3>\[\d+\] \[e\] Test 123\n$/, 'right format'; like $log->format->(time, 'fatal', 'Test 123'), qr/^<2>\[\d+\] \[f\] Test 123\n$/, 'right format'; like $log->format->(time, 'debug', 'Test', '1', '2', '3'), qr/^<6>\[\d+\] \[d\] Test 1 2 3\n$/, 'right format'; + like $log->format->(time, 'debug', undef, 'works'), qr/^<6>\[\d+\] \[d\] undef works\n$/, 'right format'; }; subtest 'Colorized log messages' => sub { diff --git a/t/mojo/parameters.t b/t/mojo/parameters.t index 0f1d6128aa..9f947413d4 100644 --- a/t/mojo/parameters.t +++ b/t/mojo/parameters.t @@ -17,6 +17,20 @@ subtest 'Basic functionality' => sub { is $params->remove('a')->to_string, 'foo=b%3Bar&baz=23&b=6&b=7&c=f%3Boo', 'right format'; }; +subtest 'Deep hash' => sub { + my $params = Mojo::Parameters->new('foo[bar]=baz&foo[bar]=yada&foo[baz][qux]=23&foo[0]=zero'); + is_deeply $params->to_hash, {'foo[bar]' => ['baz', 'yada'], 'foo[baz][qux]' => 23, 'foo[0]' => 'zero'}, + 'right shallow structure'; + is_deeply $params->to_deep_hash, {foo => {bar => ['baz', 'yada'], baz => {qux => 23}, 0 => 'zero'}}, + 'right deep structure'; + $params = Mojo::Parameters->new(foo => 'bar'); + is_deeply $params->to_deep_hash, {foo => 'bar'}, 'right shallow structure'; + $params = Mojo::Parameters->new('foo%5Bbar%5D=baz'); + is_deeply $params->to_deep_hash, {foo => {bar => 'baz'}}, 'right decoded structure'; + $params = Mojo::Parameters->new('foo[]=baz&foo[bar=baz'); + is_deeply $params->to_deep_hash, {'foo[]' => 'baz', 'foo[bar' => 'baz'}, 'right malformed structure'; +}; + subtest 'Clone' => sub { my $params = Mojo::Parameters->new('foo=b%3Bar&baz=23'); my $clone = $params->clone;