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.013';
3             # ABSTRACT: Production safe data inspector
4              
5 2     2   269002 use strict;
  2         8  
  2         46  
6 2     2   9 use warnings;
  2         5  
  2         43  
7              
8 2     2   9 use Carp qw(croak);
  2         2  
  2         71  
9 2     2   9 use Scalar::Util qw(looks_like_number);
  2         4  
  2         66  
10 2     2   822 use Ref::Util qw(is_ref is_arrayref is_hashref is_scalarref is_coderef is_regexpref);
  2         2652  
  2         143  
11              
12 2     2   406 use Sub::Info qw(sub_info);
  2         5645  
  2         13  
13              
14 2     2   775 use PadWalker qw(peek_my);
  2         974  
  2         91  
15              
16 2     2   693 use Devel::Optic::Lens::Perlish;
  2         4  
  2         72  
17              
18             use constant {
19 2         1315 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 16492 my ($class, %params) = @_;
26 9   100     29 my $uplevel = $params{uplevel} // 1;
27              
28 9 100 100     51 if (!$uplevel || !looks_like_number($uplevel) || $uplevel < 1) {
      100        
29 3         306 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     50 lens => $params{lens} // Devel::Optic::Lens::Perlish->new,
      100        
      100        
      33        
45             };
46              
47 6         15 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 72 my ($self, $subject) = @_;
63              
64 28         43 my $ref = ref $subject;
65 28   100     121 my $reasonably_summarized_with_substr = !is_ref($subject) || is_regexpref($subject) || is_scalarref($subject);
66              
67 28 100       47 if ($reasonably_summarized_with_substr) {
68 9 100       12 if (!defined $subject) {
69 1         4 return "(undef)";
70             }
71              
72 8 100       18 if ($subject eq "") {
73 1         4 return '"" (len 0)';
74             }
75              
76 7 100       11 $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       13 if ($len <= $scalar_truncation_size) {
87 5 100       46 return sprintf(
88             "%s%s (len %d)",
89             $ref ? "$ref " : "",
90             $subject,
91             $len,
92             );
93             }
94              
95 2 50       49 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         28 my $sample_count = $self->{sample_count};
105 19         22 my $scalar_sample_size = $self->{scalar_sample_size};
106 19         26 my $sample_text = "(no sample)";
107 19 100       37 if (is_hashref($subject)) {
    100          
    100          
108 9         10 my @sample;
109 9         56 my @keys = keys %$subject;
110 9         12 my $key_count = scalar @keys;
111 9 100       29 $sample_count = $key_count > $sample_count ? $sample_count : $key_count;
112 9         24 my @sample_keys = @keys[0 .. $sample_count - 1];
113 9         15 for my $key (@sample_keys) {
114 16         22 my $val = $subject->{$key};
115 16         16 my $val_chunk;
116 16 100       23 if (ref $val) {
117 4         5 $val_chunk = ref $val;
118             } else {
119 12         17 $val_chunk = substr($val, 0, $scalar_sample_size);
120 12 100       23 $val_chunk .= '...' if length($val_chunk) < length($val);
121             }
122 16         20 my $key_chunk = substr($key, 0, $scalar_sample_size);
123 16 100       23 $key_chunk .= '...' if length($key_chunk) < length($key);
124 16         44 push @sample, sprintf("%s => %s", $key_chunk, $val_chunk);
125             }
126 9 100       41 $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         9 my $total_len = scalar @$subject;
134 8 100       14 $sample_count = $total_len > $sample_count ? $sample_count : $total_len;
135 8         15 for (my $i = 0; $i < $sample_count; $i++) {
136 16         23 my $val = $subject->[$i];
137 16         19 my $val_chunk;
138 16 100       27 if (ref $val) {
139 4         5 $val_chunk = ref $val;
140             } else {
141 12         19 $val_chunk = substr($val, 0, $scalar_sample_size);
142 12 100       19 $val_chunk .= '...' if length($val_chunk) < length($val);
143             }
144 16         33 push @sample, $val_chunk;
145             }
146 8 100       55 $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         5 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         372 );
160             }
161              
162 19         169 return "$ref: $sample_text";
163             }
164              
165             1;
166              
167             __END__