File Coverage

blib/lib/Devel/Optic.pm
Criterion Covered Total %
statement 84 88 95.4
branch 43 44 97.7
condition 18 20 90.0
subroutine 11 12 91.6
pod 3 3 100.0
total 159 167 95.2


line stmt bran cond sub pod time code
1             package Devel::Optic;
2             $Devel::Optic::VERSION = '0.015';
3             # ABSTRACT: Production safe data inspector
4              
5 2     2   262017 use strict;
  2         8  
  2         46  
6 2     2   9 use warnings;
  2         3  
  2         43  
7              
8 2     2   9 use Carp qw(croak);
  2         3  
  2         68  
9 2     2   8 use Scalar::Util qw(looks_like_number);
  2         4  
  2         61  
10 2     2   805 use Ref::Util qw(is_ref is_arrayref is_hashref is_scalarref is_coderef is_regexpref);
  2         2668  
  2         139  
11              
12 2     2   403 use Sub::Info qw(sub_info);
  2         5692  
  2         11  
13              
14 2     2   756 use PadWalker qw(peek_my);
  2         986  
  2         91  
15              
16 2     2   693 use Devel::Optic::Lens::Perlish;
  2         5  
  2         59  
17              
18             use constant {
19 2         1296 DEFAULT_SCALAR_TRUNCATION_SIZE => 256,
20             DEFAULT_SCALAR_SAMPLE_SIZE => 64,
21             DEFAULT_SAMPLE_COUNT => 4,
22 2     2   10 };
  2         3  
23              
24             sub new {
25 10     10 1 17537 my ($class, %params) = @_;
26 10   100     33 my $uplevel = $params{uplevel} // 1;
27              
28 10 100 100     56 if (!$uplevel || !looks_like_number($uplevel) || $uplevel < 1) {
      100        
29 3         304 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 7   100     81 lens => $params{lens} // Devel::Optic::Lens::Perlish->new,
      100        
      100        
      33        
45             };
46              
47 7         17 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 31     31 1 74 my ($self, $subject) = @_;
63              
64 31         45 my $ref = ref $subject;
65 31   100     127 my $reasonably_summarized_with_substr = !is_ref($subject) || is_regexpref($subject) || is_scalarref($subject);
66              
67 31 100       52 if ($reasonably_summarized_with_substr) {
68 9 100       14 if (!defined $subject) {
69 1         6 return "(undef)";
70             }
71              
72 8 100       14 if ($subject eq "") {
73 1         4 return '"" (len 0)';
74             }
75              
76 7 100       13 $subject = $$subject if is_scalarref($subject);
77 7         20 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       12 if ($len <= $scalar_truncation_size) {
87 5 100       47 return sprintf(
88             "%s%s (len %d)",
89             $ref ? "$ref " : "",
90             $subject,
91             $len,
92             );
93             }
94              
95 2 50       67 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 22         27 my $sample_count = $self->{sample_count};
105 22         24 my $scalar_sample_size = $self->{scalar_sample_size};
106 22         26 my $sample_text = "(no sample)";
107 22 100       46 if (is_hashref($subject)) {
    100          
    100          
108 10         10 my @sample;
109 10         56 my @keys = keys %$subject;
110 10         15 my $key_count = scalar @keys;
111 10 100       31 $sample_count = $key_count > $sample_count ? $sample_count : $key_count;
112 10         25 my @sample_keys = @keys[0 .. $sample_count - 1];
113 10         16 for my $key (@sample_keys) {
114 17         22 my $val = $subject->{$key};
115 17         18 my $val_chunk;
116 17 100       27 if (ref $val) {
    100          
117 4         6 $val_chunk = ref $val;
118             } elsif (!defined $val) {
119 1         2 $val_chunk = '(undef)';
120             } else {
121 12         18 $val_chunk = substr($val, 0, $scalar_sample_size);
122 12 100       28 $val_chunk .= '...' if length($val_chunk) < length($val);
123             }
124 17         22 my $key_chunk = substr($key, 0, $scalar_sample_size);
125 17 100       23 $key_chunk .= '...' if length($key_chunk) < length($key);
126 17         53 push @sample, sprintf("%s => %s", $key_chunk, $val_chunk);
127             }
128 10 100       44 $sample_text = sprintf("{%s%s} (%d keys)",
129             join(', ', @sample),
130             $key_count > $sample_count ? ' ...' : '',
131             $key_count,
132             );
133             } elsif (is_arrayref($subject)) {
134 10         11 my @sample;
135 10         11 my $total_len = scalar @$subject;
136 10 100       25 $sample_count = $total_len > $sample_count ? $sample_count : $total_len;
137 10         24 for (my $i = 0; $i < $sample_count; $i++) {
138 21         24 my $val = $subject->[$i];
139 21         20 my $val_chunk;
140 21 100       36 if (ref $val) {
    100          
141 4         3 $val_chunk = ref $val;
142             } elsif (!defined $val) {
143 3         4 $val_chunk = '(undef)';
144             } else {
145 14         17 $val_chunk = substr($val, 0, $scalar_sample_size);
146 14 100       22 $val_chunk .= '...' if length($val_chunk) < length($val);
147             }
148 21         38 push @sample, $val_chunk;
149             }
150 10 100       49 $sample_text = sprintf("[%s%s] (len %d)",
151             join(', ', @sample),
152             $total_len > $sample_count ? ' ...' : '',
153             $total_len,
154             );
155             } elsif (is_coderef($subject)) {
156 1         4 my $info = sub_info($subject);
157             $sample_text = sprintf("sub %s { ... } (L%d-%d in %s (%s))",
158             $info->{name},
159             $info->{start_line},
160             $info->{end_line},
161             $info->{package},
162             $info->{file},
163 1         348 );
164             }
165              
166 22         175 return "$ref: $sample_text";
167             }
168              
169             1;
170              
171             __END__