File Coverage

blib/lib/Devel/Optic.pm
Criterion Covered Total %
statement 82 86 95.3
branch 39 40 97.5
condition 18 20 90.0
subroutine 11 12 91.6
pod 3 3 100.0
total 153 161 95.0


line stmt bran cond sub pod time code
1             package Devel::Optic;
2             $Devel::Optic::VERSION = '0.014';
3             # ABSTRACT: Production safe data inspector
4              
5 2     2   262341 use strict;
  2         8  
  2         48  
6 2     2   8 use warnings;
  2         4  
  2         44  
7              
8 2     2   8 use Carp qw(croak);
  2         3  
  2         74  
9 2     2   8 use Scalar::Util qw(looks_like_number);
  2         3  
  2         76  
10 2     2   813 use Ref::Util qw(is_ref is_arrayref is_hashref is_scalarref is_coderef is_regexpref);
  2         2705  
  2         183  
11              
12 2     2   646 use Sub::Info qw(sub_info);
  2         5965  
  2         12  
13              
14 2     2   771 use PadWalker qw(peek_my);
  2         1006  
  2         94  
15              
16 2     2   707 use Devel::Optic::Lens::Perlish;
  2         4  
  2         69  
17              
18             use constant {
19 2         1254 DEFAULT_SCALAR_TRUNCATION_SIZE => 256,
20             DEFAULT_SCALAR_SAMPLE_SIZE => 64,
21             DEFAULT_SAMPLE_COUNT => 4,
22 2     2   10 };
  2         4  
23              
24             sub new {
25 9     9 1 20130 my ($class, %params) = @_;
26 9   100     34 my $uplevel = $params{uplevel} // 1;
27              
28 9 100 100     48 if (!$uplevel || !looks_like_number($uplevel) || $uplevel < 1) {
      100        
29 3         311 croak "uplevel should be integer >= 1, not '$uplevel'";
30             }
31              
32             my $self = {
33             uplevel => $uplevel,
34              
35             # substr size for scalar subjects
36             scalar_truncation_size => $params{scalar_truncation_size} // DEFAULT_SCALAR_TRUNCATION_SIZE,
37              
38             # when building a sample, how much of each scalar child to substr
39             scalar_sample_size => $params{scalar_sample_size} // DEFAULT_SCALAR_SAMPLE_SIZE,
40              
41             # how many keys or indicies to display in a sample from a hashref/arrayref
42             sample_count => $params{sample_count} // DEFAULT_SAMPLE_COUNT,
43              
44 6   100     49 lens => $params{lens} // Devel::Optic::Lens::Perlish->new,
      100        
      100        
      33        
45             };
46              
47 6         14 bless $self, $class;
48             }
49              
50             sub inspect {
51 0     0 1 0 my ($self, $query) = @_;
52 0         0 my $scope = peek_my($self->{uplevel});
53 0         0 my $full_picture = $self->{lens}->inspect($scope, $query);
54 0         0 return $self->fit_to_view($full_picture);
55             }
56              
57             # This sub is effectively a very basic serializer. It could probably be made
58             # much more information-dense by adopting strategies from real serializers, or
59             # by incorporating hints from the user on their desired space<->thoroughness
60             # tradeoff.
61             sub fit_to_view {
62 28     28 1 70 my ($self, $subject) = @_;
63              
64 28         52 my $ref = ref $subject;
65 28   100     161 my $reasonably_summarized_with_substr = !is_ref($subject) || is_regexpref($subject) || is_scalarref($subject);
66              
67 28 100       53 if ($reasonably_summarized_with_substr) {
68 9 100       16 if (!defined $subject) {
69 1         5 return "(undef)";
70             }
71              
72 8 100       17 if ($subject eq "") {
73 1         40 return '"" (len 0)';
74             }
75              
76 7 100       12 $subject = $$subject if is_scalarref($subject);
77 7         12 my $scalar_truncation_size = $self->{scalar_truncation_size};
78 7         10 my $len = length $subject;
79              
80             # simple scalars we can truncate (PadWalker always returns refs, so
81             # this is pretty safe from accidentally substr-ing an array or hash).
82             # Also, once we know we're dealing with a gigantic string (or
83             # number...), we can trim much more aggressively without hurting user
84             # understanding too much.
85              
86 7 100       11 if ($len <= $scalar_truncation_size) {
87 5 100       61 return sprintf(
88             "%s%s (len %d)",
89             $ref ? "$ref " : "",
90             $subject,
91             $len,
92             );
93             }
94              
95 2 50       45 return sprintf(
96             "%s%s (truncated to len %d; len %d)",
97             $ref ? "$ref " : "",
98             substr($subject, 0, $scalar_truncation_size) . "...",
99             $scalar_truncation_size,
100             $len,
101             );
102             }
103              
104 19         26 my $sample_count = $self->{sample_count};
105 19         24 my $scalar_sample_size = $self->{scalar_sample_size};
106 19         24 my $sample_text = "(no sample)";
107 19 100       38 if (is_hashref($subject)) {
    100          
    100          
108 9         10 my @sample;
109 9         58 my @keys = keys %$subject;
110 9         14 my $key_count = scalar @keys;
111 9 100       28 $sample_count = $key_count > $sample_count ? $sample_count : $key_count;
112 9         23 my @sample_keys = @keys[0 .. $sample_count - 1];
113 9         16 for my $key (@sample_keys) {
114 16         23 my $val = $subject->{$key};
115 16         17 my $val_chunk;
116 16 100       19 if (ref $val) {
117 4         7 $val_chunk = ref $val;
118             } else {
119 12         19 $val_chunk = substr($val, 0, $scalar_sample_size);
120 12 100       22 $val_chunk .= '...' if length($val_chunk) < length($val);
121             }
122 16         20 my $key_chunk = substr($key, 0, $scalar_sample_size);
123 16 100       67 $key_chunk .= '...' if length($key_chunk) < length($key);
124 16         80 push @sample, sprintf("%s => %s", $key_chunk, $val_chunk);
125             }
126 9 100       46 $sample_text = sprintf("{%s%s} (%d keys)",
127             join(', ', @sample),
128             $key_count > $sample_count ? ' ...' : '',
129             $key_count,
130             );
131             } elsif (is_arrayref($subject)) {
132 8         9 my @sample;
133 8         10 my $total_len = scalar @$subject;
134 8 100       15 $sample_count = $total_len > $sample_count ? $sample_count : $total_len;
135 8         17 for (my $i = 0; $i < $sample_count; $i++) {
136 16         17 my $val = $subject->[$i];
137 16         22 my $val_chunk;
138 16 100       24 if (ref $val) {
139 4         4 $val_chunk = ref $val;
140             } else {
141 12         17 $val_chunk = substr($val, 0, $scalar_sample_size);
142 12 100       17 $val_chunk .= '...' if length($val_chunk) < length($val);
143             }
144 16         32 push @sample, $val_chunk;
145             }
146 8 100       38 $sample_text = sprintf("[%s%s] (len %d)",
147             join(', ', @sample),
148             $total_len > $sample_count ? ' ...' : '',
149             $total_len,
150             );
151             } elsif (is_coderef($subject)) {
152 1         6 my $info = sub_info($subject);
153             $sample_text = sprintf("sub %s { ... } (L%d-%d in %s (%s))",
154             $info->{name},
155             $info->{start_line},
156             $info->{end_line},
157             $info->{package},
158             $info->{file},
159 1         355 );
160             }
161              
162 19         180 return "$ref: $sample_text";
163             }
164              
165             1;
166              
167             __END__