Devel-Optic
view release on metacpan or search on metacpan
lib/Devel/Optic.pm view on Meta::CPAN
package Devel::Optic;
$Devel::Optic::VERSION = '0.015';
# ABSTRACT: Production safe data inspector
use strict;
use warnings;
use Carp qw(croak);
use Scalar::Util qw(looks_like_number);
use Ref::Util qw(is_ref is_arrayref is_hashref is_scalarref is_coderef is_regexpref);
use Sub::Info qw(sub_info);
use PadWalker qw(peek_my);
use Devel::Optic::Lens::Perlish;
use constant {
DEFAULT_SCALAR_TRUNCATION_SIZE => 256,
DEFAULT_SCALAR_SAMPLE_SIZE => 64,
DEFAULT_SAMPLE_COUNT => 4,
};
sub new {
my ($class, %params) = @_;
my $uplevel = $params{uplevel} // 1;
if (!$uplevel || !looks_like_number($uplevel) || $uplevel < 1) {
croak "uplevel should be integer >= 1, not '$uplevel'";
}
my $self = {
uplevel => $uplevel,
# substr size for scalar subjects
scalar_truncation_size => $params{scalar_truncation_size} // DEFAULT_SCALAR_TRUNCATION_SIZE,
# when building a sample, how much of each scalar child to substr
scalar_sample_size => $params{scalar_sample_size} // DEFAULT_SCALAR_SAMPLE_SIZE,
# how many keys or indicies to display in a sample from a hashref/arrayref
sample_count => $params{sample_count} // DEFAULT_SAMPLE_COUNT,
lens => $params{lens} // Devel::Optic::Lens::Perlish->new,
};
bless $self, $class;
}
sub inspect {
my ($self, $query) = @_;
my $scope = peek_my($self->{uplevel});
my $full_picture = $self->{lens}->inspect($scope, $query);
return $self->fit_to_view($full_picture);
}
# This sub is effectively a very basic serializer. It could probably be made
# much more information-dense by adopting strategies from real serializers, or
# by incorporating hints from the user on their desired space<->thoroughness
# tradeoff.
sub fit_to_view {
my ($self, $subject) = @_;
my $ref = ref $subject;
my $reasonably_summarized_with_substr = !is_ref($subject) || is_regexpref($subject) || is_scalarref($subject);
if ($reasonably_summarized_with_substr) {
if (!defined $subject) {
return "(undef)";
}
if ($subject eq "") {
return '"" (len 0)';
}
$subject = $$subject if is_scalarref($subject);
my $scalar_truncation_size = $self->{scalar_truncation_size};
my $len = length $subject;
# simple scalars we can truncate (PadWalker always returns refs, so
# this is pretty safe from accidentally substr-ing an array or hash).
# Also, once we know we're dealing with a gigantic string (or
# number...), we can trim much more aggressively without hurting user
# understanding too much.
if ($len <= $scalar_truncation_size) {
return sprintf(
"%s%s (len %d)",
$ref ? "$ref " : "",
$subject,
$len,
);
}
return sprintf(
"%s%s (truncated to len %d; len %d)",
$ref ? "$ref " : "",
substr($subject, 0, $scalar_truncation_size) . "...",
$scalar_truncation_size,
$len,
);
}
my $sample_count = $self->{sample_count};
my $scalar_sample_size = $self->{scalar_sample_size};
my $sample_text = "(no sample)";
if (is_hashref($subject)) {
my @sample;
my @keys = keys %$subject;
my $key_count = scalar @keys;
$sample_count = $key_count > $sample_count ? $sample_count : $key_count;
my @sample_keys = @keys[0 .. $sample_count - 1];
for my $key (@sample_keys) {
my $val = $subject->{$key};
my $val_chunk;
if (ref $val) {
$val_chunk = ref $val;
} elsif (!defined $val) {
$val_chunk = '(undef)';
} else {
$val_chunk = substr($val, 0, $scalar_sample_size);
$val_chunk .= '...' if length($val_chunk) < length($val);
}
my $key_chunk = substr($key, 0, $scalar_sample_size);
$key_chunk .= '...' if length($key_chunk) < length($key);
push @sample, sprintf("%s => %s", $key_chunk, $val_chunk);
}
$sample_text = sprintf("{%s%s} (%d keys)",
join(', ', @sample),
$key_count > $sample_count ? ' ...' : '',
$key_count,
);
} elsif (is_arrayref($subject)) {
my @sample;
my $total_len = scalar @$subject;
$sample_count = $total_len > $sample_count ? $sample_count : $total_len;
for (my $i = 0; $i < $sample_count; $i++) {
my $val = $subject->[$i];
my $val_chunk;
if (ref $val) {
lib/Devel/Optic.pm view on Meta::CPAN
["blub", { needle => "find me!", some_other_key => "blorb" }]
Other syntactic examples:
$hash_ref->{'a'}->[0]->[3]->{'blorg'}
@array->[0]->{'foo'}
$array_ref->[0]->{'foo'}
$scalar
=head4 QUERY SYNTAX ALTNERATIVES
The query syntax attempts to provide a reasonable amount of power
for navigating Perl data structures without risking the stability of the system
under inspection.
In other words, while C<eval '$my_cool_hash{a}-E<gt>[1]-E<gt>{needle}'> would
be a much more powerful solution to the problem of navigating Perl data
structures, it opens up all the cans of worms at once.
The current syntax might be a little bit "uncanny valley" in that it looks like
Perl, but is not Perl. It is Perl-ish. It also might be too complex, since it
allows fancy things like nested resolution:
$foo->{$bar}
Or even:
%my_hash->{$some_arrayref->[$some_scalar->{'key'}]}->{'needle'}
Ouch. In practice I hope and expect that the majority of queries will be
simple scalars, or maybe one or two chained hashkey/array index lookups.
I'm open to exploring other syntax in this area as long as it is aligned with
the following goals:
=over 4
=item Simple query model
As a debugging tool, you have enough on your brain just debugging your system.
Second-guessing your query syntax when you get unexpected results is a major
distraction and leads to loss of trust in the tool (I'm looking at you,
ElasticSearch).
=item O(1), not O(n) (or worse)
I'd like to avoid globs or matching syntax that might end up iterating over
unbounded chunks of a data structure. Traversing a small, fixed number of keys
in 'parallel' sounds like a sane extension, but anything which requires
iterating over the entire set of hash keys or array indicies is likely to
surprise when debugging systems with unexpectedly large data structures.
=back
=head1 SEE ALSO
=over 4
=item *
L<PadWalker>
=item *
L<Devel::Probe>
=back
=head1 AUTHOR
Ben Tyler <btyler@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2019 by Ben Tyler.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
( run in 1.379 second using v1.01-cache-2.11-cpan-e1769b4cff6 )