| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Data::Show; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=encoding utf-8 |
|
4
|
|
|
|
|
|
|
=cut |
|
5
|
|
|
|
|
|
|
|
|
6
|
12
|
|
|
12
|
|
905814
|
use 5.010; |
|
|
12
|
|
|
|
|
48
|
|
|
7
|
12
|
|
|
11
|
|
161
|
use strict; |
|
|
11
|
|
|
|
|
38
|
|
|
|
11
|
|
|
|
|
318
|
|
|
8
|
11
|
|
|
11
|
|
65
|
use warnings; |
|
|
11
|
|
|
|
|
22
|
|
|
|
11
|
|
|
|
|
849
|
|
|
9
|
11
|
|
|
11
|
|
6001
|
use utf8; |
|
|
11
|
|
|
|
|
3486
|
|
|
|
11
|
|
|
|
|
78
|
|
|
10
|
11
|
|
|
11
|
|
15972
|
use PPR; |
|
|
11
|
|
|
|
|
824839
|
|
|
|
11
|
|
|
|
|
1486
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = '0.004002'; |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# Be a ninja... |
|
15
|
|
|
|
|
|
|
our @CARP_NOT; |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Useful pieces of information... |
|
18
|
11
|
100
|
|
11
|
|
103
|
my $IS_UTF8_TERM; BEGIN { $IS_UTF8_TERM = grep {$_ && /utf-8/i} @ENV{qw}; } |
|
|
33
|
|
|
|
|
841
|
|
|
19
|
11
|
|
50
|
11
|
|
666
|
my $IS_LIGHT_BG; BEGIN { $IS_LIGHT_BG = ($ENV{COLORFGBG} // q{}) =~ m{\A 0;15 \z}x; } |
|
20
|
11
|
50
|
|
11
|
|
27
|
my $CAN_ANSICOLOR; BEGIN { $CAN_ANSICOLOR = eval { require Term::ANSIColor; 1 } ? 1 : 0; } |
|
|
11
|
|
|
|
|
8032
|
|
|
|
11
|
|
|
|
|
111169
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# Various defaults... |
|
23
|
11
|
|
|
11
|
|
385
|
my $MAXWIDTH; BEGIN { $MAXWIDTH = 78; } |
|
24
|
11
|
|
|
11
|
|
245
|
my $INITIAL_DEFAULT_PLUGIN; BEGIN { $INITIAL_DEFAULT_PLUGIN = 'Data::Show::Plugin::Data::Pretty'; } |
|
25
|
11
|
|
|
11
|
|
314
|
my $FINAL_CANDIDATE_PLUGIN; BEGIN { $FINAL_CANDIDATE_PLUGIN = 'Data::Show::Plugin'; } |
|
26
|
11
|
|
|
11
|
|
225
|
my $DEFAULT_TARGET; BEGIN { $DEFAULT_TARGET = \*STDERR; } |
|
27
|
11
|
|
|
11
|
|
276
|
my $RC_FILE_NAME; BEGIN { $RC_FILE_NAME = '.datashow'; } |
|
28
|
11
|
|
|
11
|
|
656
|
my @PLUGIN_API; BEGIN { @PLUGIN_API = qw< stringify format >; } |
|
29
|
11
|
|
|
11
|
|
920
|
my @ARGUMENT_DEFAULTS; BEGIN { @ARGUMENT_DEFAULTS = ( |
|
30
|
|
|
|
|
|
|
to => $DEFAULT_TARGET, |
|
31
|
|
|
|
|
|
|
with => $INITIAL_DEFAULT_PLUGIN, |
|
32
|
|
|
|
|
|
|
as => 'show', |
|
33
|
|
|
|
|
|
|
fallback => q{}, |
|
34
|
|
|
|
|
|
|
warnings => 'off', |
|
35
|
|
|
|
|
|
|
termwidth => $MAXWIDTH, |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
grid => 'off', |
|
38
|
|
|
|
|
|
|
style => 'auto', |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# DARK BACKGROUND LIGHT BACKGROUND |
|
41
|
|
|
|
|
|
|
showstyle => 'bold bright_cyan , bold bright_blue', |
|
42
|
|
|
|
|
|
|
datastyle => 'bold white , bold black', |
|
43
|
|
|
|
|
|
|
codestyle => 'cyan , blue', |
|
44
|
|
|
|
|
|
|
filestyle => 'blue , red', |
|
45
|
|
|
|
|
|
|
linestyle => 'blue , red', |
|
46
|
|
|
|
|
|
|
gridstyle => 'blue , red', |
|
47
|
|
|
|
|
|
|
); |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
my %GRID; BEGIN { |
|
50
|
11
|
100
|
|
11
|
|
1273
|
@GRID{split //, q{┏┯┓┗┷┛━┃┠─┬┴┨│} } |
|
51
|
|
|
|
|
|
|
= split //, ( $IS_UTF8_TERM ? q{┏┯┓┗┷┛━┃┠─┬┴┨│} |
|
52
|
|
|
|
|
|
|
: q{ _ |_|_||---|:} |
|
53
|
|
|
|
|
|
|
); |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Useful regexes... |
|
58
|
11
|
|
|
11
|
|
2269
|
my $OWS; BEGIN { $OWS = qr{ (?: \s++ | \# [^\n]*+ )*+ }x; } |
|
59
|
11
|
|
|
11
|
|
631
|
my $IDENT; BEGIN { $IDENT = qr{ [^\W\d]\w* (?: :: [^\W\d]\w* )* | [_\W] }x; } |
|
60
|
11
|
|
|
11
|
|
1392
|
my $COLOUR_CHAR; BEGIN { $COLOUR_CHAR = qr{ (?: \e[^m]*m )* [^\n] (?: \e[^m]*m )* }x; } |
|
61
|
11
|
|
|
11
|
|
1241
|
my $VALID_ARG; BEGIN { $VALID_ARG = qr{ \A (?: to | with | fallback |
|
62
|
|
|
|
|
|
|
| base | warnings | as |
|
63
|
|
|
|
|
|
|
| style | grid | termwidth |
|
64
|
|
|
|
|
|
|
| datastyle | filestyle | linestyle |
|
65
|
|
|
|
|
|
|
| codestyle | showstyle | gridstyle |
|
66
|
|
|
|
|
|
|
) \z }x; } |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Track lexically scoped output targets and styles... |
|
69
|
|
|
|
|
|
|
my @OUTPUT_FH; |
|
70
|
|
|
|
|
|
|
my @STYLE; |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Export the module's API, or that of a plugin (as requested)... |
|
73
|
|
|
|
|
|
|
sub import { |
|
74
|
|
|
|
|
|
|
# Track load context... |
|
75
|
11
|
|
|
11
|
|
152
|
my ($package, $file, $line) = _get_context(); |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Remove the module name from the argument list... |
|
78
|
11
|
|
|
|
|
27
|
shift @_; |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Handle the special case of a 'base' argument (by adding it as the caller's base class)... |
|
81
|
11
|
50
|
66
|
|
|
74
|
if (@_ > 0 && $_[0] eq 'base') { |
|
82
|
0
|
0
|
|
|
|
0
|
die "If 'base' is specified, it must be the only argument at $file line $line\n" if @_ > 2; |
|
83
|
11
|
|
|
11
|
|
165
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
5583
|
|
|
84
|
0
|
|
0
|
|
|
0
|
push @{caller().'::ISA'}, _load_plugin( $_[1] // 'Data::Show::Plugin', $file, $line, 'warn' ); |
|
|
0
|
|
|
|
|
0
|
|
|
85
|
0
|
|
|
|
|
0
|
return; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Check for missing named args and improve the usual warning for that problem... |
|
89
|
11
|
50
|
|
|
|
51
|
die "No value specified for named argument '$_[-1]' at $file line $line\n" |
|
90
|
|
|
|
|
|
|
if @_ % 2 != 0; |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# Unpack args (including defaults from config file).... |
|
93
|
11
|
|
|
|
|
42
|
state $defaults_ref = _load_defaults($file, $line); |
|
94
|
11
|
|
|
|
|
28
|
my %opt = (%{$defaults_ref}, @_); |
|
|
11
|
|
|
|
|
112
|
|
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Punish invalid arguments... |
|
97
|
11
|
|
|
|
|
75
|
_validate_args(\%opt, "at $file line $line", "named argument"); |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Any 'to' arg must be a filehandle, filename, or scalar ref (and open it if necessary)... |
|
100
|
11
|
|
33
|
|
|
72
|
$opt{to} = _open_target( $opt{to} // $DEFAULT_TARGET, $file, $line, $opt{warnings} ne 'off' ); |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Unpack fallback arguments into an arrayref... |
|
103
|
11
|
|
|
|
|
50
|
$opt{fallback} = [ split m{ \s*,\s* }x, $opt{fallback} ]; |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Resolve style options according to terminal background (i.e. dark or light) |
|
106
|
11
|
|
|
|
|
169
|
for my $option (@opt{ grep /\A.+style\z/, keys %opt}) { |
|
107
|
66
|
50
|
|
|
|
366
|
$option = [split /\s*,\s*/, $option]->[$IS_LIGHT_BG ? -1 : 0]; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Install Data::Show::Plugin base class as well... |
|
111
|
11
|
|
|
|
|
47
|
$INC{'Data/Show/Plugin.pm'} = $INC{'Data/Show.pm'}; |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Track lexical options... |
|
114
|
|
|
|
|
|
|
$^H{'Data::Show/with'} = _load_plugin( $opt{with}, $file, $line, |
|
115
|
11
|
|
|
|
|
51
|
$opt{warnings} ne 'off', $opt{fallback} ); |
|
116
|
11
|
|
|
|
|
50
|
$^H{'Data::Show/termwidth'} = $opt{termwidth}; |
|
117
|
11
|
|
|
|
|
40
|
$^H{'Data::Show/to'} = @OUTPUT_FH; |
|
118
|
11
|
|
|
|
|
37
|
$^H{'Data::Show/style'} = @STYLE; |
|
119
|
11
|
|
100
|
|
|
106
|
my $existing_as = $^H{'Data::Show/as'} // '(?!)'; |
|
120
|
11
|
|
|
|
|
67
|
$^H{'Data::Show/as'} = "$existing_as|$opt{as}"; |
|
121
|
11
|
|
|
|
|
43
|
push @OUTPUT_FH, $opt{to}; |
|
122
|
|
|
|
|
|
|
push @STYLE, { add_grid => $opt{grid}, |
|
123
|
|
|
|
|
|
|
mode => $opt{style}, |
|
124
|
11
|
100
|
|
|
|
93
|
map { m/(.+)style/ ? ($1 => $opt{$_}) : () } keys %opt |
|
|
154
|
|
|
|
|
624
|
|
|
125
|
|
|
|
|
|
|
}; |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# Install the function... |
|
128
|
11
|
|
|
11
|
|
88
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
20
|
|
|
|
11
|
|
|
|
|
2136
|
|
|
129
|
11
|
|
|
|
|
57
|
*{caller() . '::' . $opt{as}} = \&show; |
|
|
11
|
|
|
|
|
12393
|
|
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# A "no Data::Show" turns show() into a no-op... |
|
133
|
|
|
|
|
|
|
sub unimport { |
|
134
|
|
|
|
|
|
|
# Track disabling lexically... |
|
135
|
1
|
|
|
1
|
|
16
|
$^H{'Data::Show/noshow'} = 1; |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Install the function... |
|
138
|
11
|
|
|
11
|
|
77
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
4788
|
|
|
139
|
1
|
|
|
|
|
4
|
*{caller() . '::show'} = \&show; |
|
|
1
|
|
|
|
|
30
|
|
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _validate_args { |
|
143
|
11
|
|
|
11
|
|
31
|
my ($opt_ref, $where, $what) = @_; |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Collect and report non-valid arguments... |
|
146
|
11
|
|
|
|
|
23
|
my @unknown_args = grep { !m{$VALID_ARG} } keys %{$opt_ref}; |
|
|
154
|
|
|
|
|
551
|
|
|
|
11
|
|
|
|
|
45
|
|
|
147
|
|
|
|
|
|
|
die "Unknown $what" . (@unknown_args == 1 ? '' : 's') . " $where:\n", |
|
148
|
11
|
0
|
|
|
|
52
|
join q{}, map { " $_\n" } @unknown_args |
|
|
0
|
50
|
|
|
|
0
|
|
|
149
|
|
|
|
|
|
|
if @unknown_args; |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# By the time we're validating, we shouldn't see a 'base' option... |
|
152
|
11
|
50
|
|
|
|
55
|
return if !exists $opt_ref->{base}; |
|
153
|
0
|
0
|
|
|
|
0
|
die $what eq 'named argument' ? "If 'base' is specified, it must be the only argument $where\n" |
|
154
|
|
|
|
|
|
|
: "Can't specify 'base' as a $what $where\n" |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# Ensure output filehandles are valid (or fall back to the default)... |
|
158
|
|
|
|
|
|
|
sub _open_target { |
|
159
|
11
|
|
|
11
|
|
39
|
my ($target, $file, $line, $warnings) = @_; |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Track already opened targets, and reuse them... |
|
162
|
11
|
|
|
|
|
21
|
state %already_open; |
|
163
|
11
|
100
|
|
|
|
45
|
return $already_open{$target} if $already_open{$target}; |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Handle stringy filenames and in-memory targets... |
|
166
|
10
|
|
|
|
|
32
|
my $to_type = ref($target); |
|
167
|
10
|
50
|
33
|
|
|
123
|
if (!$to_type && ref(\$target) ne 'GLOB' || $to_type eq 'SCALAR') { |
|
|
|
50
|
33
|
|
|
|
|
|
168
|
0
|
0
|
|
|
|
0
|
if (open my $fh, '>', $target) { |
|
169
|
0
|
|
|
|
|
0
|
return ($already_open{$target} = $fh); |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
else { |
|
172
|
0
|
0
|
|
|
|
0
|
warn "Could not open named 'to' argument for output at $file line $line\n" |
|
173
|
|
|
|
|
|
|
if $warnings; |
|
174
|
0
|
|
|
|
|
0
|
return ($already_open{$target} = $DEFAULT_TARGET); |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Handle filehandle-y targets... |
|
179
|
|
|
|
|
|
|
elsif (_is_writeable($target)) { |
|
180
|
10
|
|
|
|
|
62
|
return ($already_open{$target} = $target); |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
else { |
|
183
|
0
|
0
|
|
|
|
0
|
warn "Named 'to' argument is not a writeable target at $file line $line\n" |
|
184
|
|
|
|
|
|
|
if $warnings; |
|
185
|
0
|
|
|
|
|
0
|
return ($already_open{$target} = $DEFAULT_TARGET); |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# -w is not reliable, so do this instead... |
|
190
|
|
|
|
|
|
|
sub _is_writeable { |
|
191
|
11
|
|
|
11
|
|
115
|
return eval { no warnings; print {$_[0]} q{} }; |
|
|
11
|
|
|
10
|
|
23
|
|
|
|
11
|
|
|
|
|
15416
|
|
|
|
10
|
|
|
|
|
27
|
|
|
|
10
|
|
|
|
|
22
|
|
|
|
10
|
|
|
|
|
61
|
|
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Extract call context, adjusting for evals... |
|
196
|
|
|
|
|
|
|
sub _get_context { |
|
197
|
|
|
|
|
|
|
# Start in the current caller's caller... |
|
198
|
109
|
|
|
109
|
|
1207
|
my ($package, $file, $line, $hints_ref) = (caller(1))[0..2,10]; |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# Keep looking up as long as next caller is a string eval... |
|
201
|
109
|
50
|
|
|
|
569
|
if ($file =~ m{\A \( eval \s+ \d+ \)}x) { |
|
202
|
0
|
|
|
|
|
0
|
for my $uplevel (2..1_000_000) { |
|
203
|
0
|
|
|
|
|
0
|
my ($uppackage, $upfile, $upline) = caller($uplevel); |
|
204
|
0
|
|
|
|
|
0
|
$upfile =~ s{.*/}{}; |
|
205
|
0
|
|
|
|
|
0
|
$file .= ", at $upfile line $upline"; |
|
206
|
0
|
0
|
|
|
|
0
|
last if $upfile !~ m{\A \( eval \s+ \d+ \)}x; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
|
|
210
|
109
|
|
|
|
|
403
|
return ($package, $file, $line, $hints_ref); |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Strip repeated arguments from return list... |
|
214
|
|
|
|
|
|
|
sub _uniq { |
|
215
|
11
|
|
|
11
|
|
23
|
my %seen; |
|
216
|
11
|
|
|
|
|
33
|
return grep {!$seen{$_}++} @_; |
|
|
1
|
|
|
|
|
7
|
|
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Load requested plugin (or fall back on a safe default)... |
|
220
|
|
|
|
|
|
|
my %STANDARD_PLUGIN; # (Populated below) |
|
221
|
|
|
|
|
|
|
sub _load_plugin { |
|
222
|
11
|
|
|
11
|
|
41
|
my ($plugin, $file, $line, $warnings, $fallback_ref) = @_; |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Build initial fallback list... |
|
225
|
11
|
|
50
|
|
|
21
|
my @fallbacks = _uniq( @{ $fallback_ref // [] } ); |
|
|
11
|
|
|
|
|
55
|
|
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# Remember and normalize the original plugin requested (even after we start falling back)... |
|
228
|
11
|
|
|
|
|
24
|
my $starting_plugin = $plugin; |
|
229
|
11
|
100
|
|
|
|
47
|
if ($starting_plugin !~ m{ \A Data::Show::Plugin \b }x) { |
|
230
|
6
|
|
|
|
|
14
|
$starting_plugin = "Data::Show::Plugin::$plugin"; |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# Track outcomes... |
|
234
|
11
|
|
|
|
|
17
|
my @failed_loads; |
|
235
|
11
|
|
|
|
|
19
|
state %loaded; |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# Loop to accommodate fallbacks (if required)... |
|
238
|
|
|
|
|
|
|
CANDIDATE: |
|
239
|
11
|
|
|
|
|
23
|
while (1) { |
|
240
|
|
|
|
|
|
|
# Normalize plugin name under the Data::Show::Plugin:: hierarchy... |
|
241
|
16
|
100
|
|
|
|
49
|
if ($plugin !~ m{ \A Data::Show::Plugin \b }x) { |
|
242
|
11
|
|
|
|
|
35
|
$plugin = "Data::Show::Plugin::$plugin"; |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# Only load (or try to load) each plugin once (if already loaded, just return its name)... |
|
246
|
16
|
100
|
|
|
|
55
|
last CANDIDATE if exists $loaded{$plugin}; |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Handle standard plugins... |
|
249
|
15
|
100
|
|
|
|
57
|
if (my $standard = $STANDARD_PLUGIN{$plugin}) { |
|
|
|
50
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Validate the plugin's preconditions... |
|
252
|
14
|
|
|
|
|
26
|
for my $requirement (@{$standard->{requires}}) { |
|
|
14
|
|
|
|
|
43
|
|
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# If plugin can't be used, fall back to the next best alternative (if any)... |
|
255
|
16
|
100
|
|
|
|
1363
|
if (!eval "require $requirement; 1") { |
|
256
|
4
|
50
|
|
|
|
25
|
warn "$plugin requires $requirement, which could not be loaded.\n" |
|
257
|
|
|
|
|
|
|
if $warnings; |
|
258
|
4
|
|
|
|
|
15
|
push @failed_loads, $plugin; |
|
259
|
4
|
50
|
33
|
|
|
71
|
if ($plugin = shift(@fallbacks) // $standard->{fallback}) { |
|
260
|
4
|
|
|
|
|
30
|
next CANDIDATE; |
|
261
|
|
|
|
|
|
|
} |
|
262
|
|
|
|
|
|
|
else { |
|
263
|
0
|
|
|
|
|
0
|
$plugin = $FINAL_CANDIDATE_PLUGIN; |
|
264
|
0
|
|
|
|
|
0
|
last CANDIDATE; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Instantiate the plugin class, inserting the plugin-specific source code... |
|
270
|
10
|
50
|
0
|
10
|
|
476
|
eval qq{ |
|
|
6
|
|
|
6
|
|
45
|
|
|
|
6
|
|
|
42
|
|
10
|
|
|
|
6
|
|
|
|
|
1397
|
|
|
|
10
|
|
|
|
|
1145
|
|
|
|
42
|
|
|
|
|
249
|
|
|
|
28
|
|
|
|
|
67
|
|
|
271
|
|
|
|
|
|
|
package $plugin; |
|
272
|
|
|
|
|
|
|
BEGIN { our \@ISA = 'Data::Show::Plugin'; } |
|
273
|
|
|
|
|
|
|
$standard->{source}; |
|
274
|
|
|
|
|
|
|
1; |
|
275
|
|
|
|
|
|
|
} or die "Internal error: $@"; # This can never happen! ;-) |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# And we're done... |
|
278
|
10
|
|
|
|
|
45
|
last CANDIDATE; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# Otherwise, if we can load the (non-standard) module then we're also done... |
|
282
|
|
|
|
|
|
|
elsif (_load_external_plugin($plugin, $warnings)) { |
|
283
|
0
|
|
|
|
|
0
|
last CANDIDATE; |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Otherwise, fall back to a specified alternative, or else try the standard fallback(s)... |
|
287
|
|
|
|
|
|
|
else { |
|
288
|
1
|
50
|
|
|
|
3
|
warn "Could not install $plugin at $file line $line\n" |
|
289
|
|
|
|
|
|
|
if $warnings; |
|
290
|
1
|
|
|
|
|
3
|
push @failed_loads, $plugin; |
|
291
|
1
|
|
33
|
|
|
4
|
$plugin = shift(@fallbacks) // $INITIAL_DEFAULT_PLUGIN; |
|
292
|
1
|
|
|
|
|
4
|
next CANDIDATE; |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# Report substitution-on-failure (if any)... |
|
297
|
11
|
50
|
33
|
|
|
61
|
warn "Used $plugin in place of $starting_plugin at $file line $line\n" |
|
298
|
|
|
|
|
|
|
if $warnings && $plugin ne $starting_plugin; |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# Remember the outcome(s) to speed things up next time... |
|
301
|
11
|
|
|
|
|
55
|
$loaded{$_} = $plugin for $plugin, @failed_loads; |
|
302
|
|
|
|
|
|
|
|
|
303
|
11
|
|
|
|
|
72
|
return $plugin; |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Load or otherwise verify the availability of a non-standard plugin... |
|
307
|
|
|
|
|
|
|
sub _load_external_plugin { |
|
308
|
29
|
|
|
1
|
|
261
|
my ($plugin, $warnings) = @_; |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# Load it (or fail silently)... |
|
311
|
29
|
|
|
|
|
245
|
eval "require $plugin"; |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Are all the essential methods present in the plugin class??? |
|
314
|
29
|
|
|
|
|
2669
|
my @missing_methods = grep { !$plugin->can($_) } @PLUGIN_API; |
|
|
16
|
|
|
|
|
99
|
|
|
315
|
|
|
|
|
|
|
warn "Requested plugin class $plugin does not provide the following essential methods:\n", |
|
316
|
15
|
50
|
33
|
|
|
64
|
(map { " $_()" } @missing_methods), "\n" |
|
|
14
|
|
|
|
|
40
|
|
|
317
|
|
|
|
|
|
|
if $warnings && @missing_methods; |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# Succeed if all the essential methods are available... |
|
320
|
15
|
|
|
|
|
55
|
return !@missing_methods; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Locate and process config file(s) and/or environment variable, if any... |
|
324
|
|
|
|
|
|
|
sub _load_defaults { |
|
325
|
10
|
|
|
10
|
|
31
|
my ($file, $line) = @_; |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Build up defaults, starting with the built-in defaults... |
|
328
|
24
|
|
|
|
|
262
|
my %defaults = @ARGUMENT_DEFAULTS; |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Overwrite previous defaults with any readable global or local config file(s)... |
|
331
|
24
|
|
|
|
|
109
|
for my $config_file (grep {-r} "$ENV{HOME}/$RC_FILE_NAME", "./$RC_FILE_NAME") { |
|
|
20
|
|
|
|
|
419
|
|
|
332
|
14
|
|
|
|
|
38
|
%defaults = ( %defaults, _load_config($config_file) ); |
|
333
|
|
|
|
|
|
|
} |
|
334
|
|
|
|
|
|
|
|
|
335
|
10
|
|
|
|
|
43
|
return \%defaults; |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
sub _load_config { |
|
339
|
14
|
|
|
0
|
|
44
|
my ($filename) = @_; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# Grab contents of file... |
|
342
|
14
|
50
|
|
|
|
50
|
open my $fh, '<:utf8', $filename or return; |
|
343
|
14
|
|
|
|
|
84
|
local $/; |
|
344
|
14
|
|
0
|
|
|
55
|
my $config = readline($fh) // return; |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# Remove empty lines (including comment lines)... |
|
347
|
14
|
|
|
|
|
65
|
$config =~ s{ ^ \s* (?: \# [^\n]* )? (?:\n|\z) }{}gxms; |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# Extract keys and values of each option... |
|
350
|
14
|
|
|
|
|
226
|
my %opt = $config =~ m{ ^ \h* ([^:=]*?) \h* [:=] \h* ([^\n]*) (?:\n|\z) }gxms; |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# Convert a "*NAME" string to the corresponding named filehandle... |
|
353
|
0
|
50
|
0
|
|
|
0
|
if (exists $opt{to} && $opt{to} =~ m{ \A \* (.*) }x) { |
|
354
|
11
|
|
|
11
|
|
89
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
481
|
|
|
355
|
11
|
|
|
11
|
|
55
|
no warnings 'once'; |
|
|
11
|
|
|
|
|
16
|
|
|
|
11
|
|
|
|
|
7191
|
|
|
356
|
0
|
|
|
|
|
0
|
$opt{to} = \*{$1}; |
|
|
0
|
|
|
|
|
0
|
|
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Validate config... |
|
360
|
0
|
|
|
|
|
0
|
_validate_args(\%opt, "in $filename", "configuration option"); |
|
361
|
|
|
|
|
|
|
|
|
362
|
0
|
|
|
|
|
0
|
return %opt; |
|
363
|
|
|
|
|
|
|
} |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# The whole point of the module... |
|
366
|
|
|
|
|
|
|
sub show { |
|
367
|
|
|
|
|
|
|
# Find the various contexts of this call... |
|
368
|
98
|
|
|
98
|
1
|
1559337
|
my ($package, $file, $line, $hints_ref) = _get_context(); |
|
369
|
98
|
|
|
|
|
254
|
my $call_context = wantarray(); |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# Skip almost everything if "no Data::Show"... |
|
372
|
98
|
100
|
|
|
|
394
|
if (!$hints_ref->{'Data::Show/noshow'}) { |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# Identify current lexically-scoped config (should already have been loaded by import())... |
|
375
|
84
|
|
33
|
|
|
367
|
my $plugin_class = $hints_ref->{'Data::Show/with'} // $FINAL_CANDIDATE_PLUGIN; |
|
376
|
84
|
|
|
|
|
189
|
my %style = %{ $STYLE[ $hints_ref->{'Data::Show/style'} ] }; |
|
|
84
|
|
|
|
|
1036
|
|
|
377
|
84
|
|
|
|
|
246
|
my $termwidth = $hints_ref->{'Data::Show/termwidth'}; |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# Warn about side-effects of multi-arg calls to show() in scalar context... |
|
380
|
84
|
0
|
33
|
|
|
317
|
if (defined $call_context && !$call_context && @_ > 1) { |
|
|
|
|
33
|
|
|
|
|
|
381
|
0
|
|
|
|
|
0
|
warn "Call to show() may not be not transparent at $file line $line\n"; |
|
382
|
|
|
|
|
|
|
} |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# Serialize Contextual::Return objects (can break some dumpers in the Data::Dump family)... |
|
385
|
84
|
|
|
|
|
249
|
my @data = map { ref() =~ m{\AContextual::Return::Value} |
|
386
|
166
|
50
|
|
|
|
605
|
? do {my $v = $_->Contextual::Return::DUMP(); $v =~ s[\}\n][\},\n]gxms; eval $v; } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
387
|
|
|
|
|
|
|
: $_ |
|
388
|
|
|
|
|
|
|
} @_; |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# Extract the originating source line(s)... |
|
391
|
|
|
|
|
|
|
my ($pre_source, $source, $post_source, $startline) |
|
392
|
84
|
|
|
|
|
343
|
= _get_source($file, $line, $hints_ref->{'Data::Show/as'}); |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# What kind of data is it??? |
|
395
|
84
|
|
|
|
|
658
|
my $is_single_hash = _data_is_single_hash($source, \@data, $hints_ref->{'Data::Show/as'}); |
|
396
|
84
|
|
|
|
|
403
|
my $is_single_arg = @data == 1; |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# Stringify the data... |
|
399
|
84
|
100
|
|
|
|
2191
|
my $data = $plugin_class->stringify( $is_single_hash ? {@data} |
|
|
|
100
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
: $is_single_arg ? $data[0] |
|
401
|
|
|
|
|
|
|
: \@data |
|
402
|
|
|
|
|
|
|
); |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# Some stringifiers add an (unwanted) empty first line, so remove it... |
|
405
|
84
|
|
|
|
|
4314
|
$data =~ s{ \A \h* \n }{}xms; |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
# Change delimters of any stringified arguments that were passed to the stringifier via refs... |
|
408
|
84
|
100
|
|
|
|
396
|
if ($is_single_hash) { $data =~ s{ \A (\s*) \{ (.*) \} (\s*) \z }{$1($2)$3}xms; } |
|
|
6
|
100
|
|
|
|
84
|
|
|
409
|
24
|
|
|
|
|
302
|
elsif (!$is_single_arg) { $data =~ s{ \A (\s*) \[ (.*) \] (\s*) \z }{$1($2)$3}xms; } |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# Where are we printing to??? |
|
412
|
84
|
50
|
|
|
|
594
|
my $fh = exists $hints_ref->{'Data::Show/to'} ? $OUTPUT_FH[$hints_ref->{'Data::Show/to'}] |
|
413
|
|
|
|
|
|
|
: $DEFAULT_TARGET; |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# Disable styling if not outputting to a terminal or if styling is unavailable... |
|
416
|
84
|
50
|
0
|
|
|
534
|
if (!-t $fh || $style{mode} eq 'auto' && !$CAN_ANSICOLOR) { |
|
|
|
|
33
|
|
|
|
|
|
417
|
84
|
|
|
|
|
273
|
$style{mode} = 'off' |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Show the data with its context header (with style!)... |
|
421
|
11
|
|
|
11
|
|
87
|
no warnings 'utf8'; |
|
|
11
|
|
|
|
|
17
|
|
|
|
11
|
|
|
|
|
5748
|
|
|
422
|
84
|
|
|
|
|
206
|
print {$fh} |
|
|
84
|
|
|
|
|
1007
|
|
|
423
|
|
|
|
|
|
|
$plugin_class->format( |
|
424
|
|
|
|
|
|
|
$file, $startline, $pre_source, $source, $post_source, $data, \%style, $termwidth, |
|
425
|
|
|
|
|
|
|
); |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Return the entire argument list if possible, otherwise simulate scalar context... |
|
429
|
98
|
50
|
|
|
|
624
|
return @_ if $call_context; |
|
430
|
98
|
|
|
|
|
583
|
return $_[0]; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# Return the source code at a given file and line... |
|
434
|
|
|
|
|
|
|
sub _get_source { |
|
435
|
84
|
|
|
84
|
|
296
|
my ($file, $line, $subname) = @_; |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# Optimize look-up via a cache... |
|
438
|
84
|
|
|
|
|
163
|
state %source_cache; |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Load the entire source of requested file... |
|
441
|
84
|
100
|
|
|
|
385
|
if (!$source_cache{$file}) { |
|
442
|
|
|
|
|
|
|
# Load the source of an eval()... |
|
443
|
6
|
50
|
|
|
|
505
|
if ($file =~ m{\A \( eval \s+ \d+ \)}x) { |
|
|
|
50
|
|
|
|
|
|
|
444
|
0
|
|
|
|
|
0
|
$source_cache{$file} = (caller(2))[6]; |
|
445
|
|
|
|
|
|
|
} |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# Otherwise, read in the source from the file... |
|
448
|
|
|
|
|
|
|
elsif (open my $filehandle, '<', $file) { |
|
449
|
6
|
|
|
|
|
19
|
$source_cache{$file} = do { local $/, readline($filehandle) }; |
|
|
6
|
|
|
|
|
349
|
|
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
else { |
|
453
|
|
|
|
|
|
|
# Otherwise, see if it's a #line trick in the main file... |
|
454
|
0
|
0
|
0
|
|
|
0
|
if (!defined $source_cache{$0} && open my $selfhandle, '<', $0) { |
|
455
|
0
|
|
|
|
|
0
|
$source_cache{$0} = do { local $/, readline($selfhandle) }; |
|
|
0
|
|
|
|
|
0
|
|
|
456
|
|
|
|
|
|
|
} |
|
457
|
|
|
|
|
|
|
|
|
458
|
0
|
|
|
|
|
0
|
$source_cache{$file} = $source_cache{$0}; |
|
459
|
|
|
|
|
|
|
$source_cache{$file} =~ s{ \A .*? ^ \# \h* line \h+ (\d+) \h+ \Q$file\E \h* \n } |
|
460
|
0
|
|
|
|
|
0
|
{ "\n" x ($1-1) }xmse |
|
461
|
0
|
0
|
|
|
|
0
|
or $source_cache{$file} = q{}; |
|
462
|
0
|
|
|
|
|
0
|
$source_cache{$file} =~ s{ \A .*? ^ \# \h* line \h+ (\d+) \h+ [^\n]* \n .* }{}xms; |
|
463
|
|
|
|
|
|
|
} |
|
464
|
|
|
|
|
|
|
} |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# This pattern detects when we have a complete show() call... |
|
467
|
84
|
|
|
|
|
168
|
state %SHOW_PATTERN_FOR; |
|
468
|
84
|
|
66
|
|
|
383322
|
my $SHOW_PATTERN = $SHOW_PATTERN_FOR{$subname} |
|
469
|
|
|
|
|
|
|
//= qr{ (? [^\n]*? ) |
|
470
|
|
|
|
|
|
|
(?> |
|
471
|
|
|
|
|
|
|
(? \b(?:$subname)\b (?&PerlOWS) (?&PerlParenthesesList) ) |
|
472
|
|
|
|
|
|
|
(? [^\n]* ) |
|
473
|
|
|
|
|
|
|
| |
|
474
|
|
|
|
|
|
|
(? \b(?:$subname)\b (?&PerlOWS) (?&PerlCommaList) ) |
|
475
|
|
|
|
|
|
|
(?> (? (?&PerlOWS) (?: ; | \Z ) ) |
|
476
|
|
|
|
|
|
|
| (?) (?= (?&PerlOWS) \} ) |
|
477
|
|
|
|
|
|
|
) |
|
478
|
|
|
|
|
|
|
) |
|
479
|
|
|
|
|
|
|
$PPR::GRAMMAR |
|
480
|
|
|
|
|
|
|
}xms; |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
# Locate the call in the source code (allowing for inaccuracies in caller() line results)... |
|
483
|
11
|
|
|
11
|
|
94
|
use re 'eval'; |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
4930
|
|
|
484
|
84
|
|
|
|
|
1741
|
our $prelim_lines; local $prelim_lines = $line-1; |
|
|
84
|
|
|
|
|
429
|
|
|
485
|
84
|
|
|
|
|
5128946
|
my $found = $source_cache{$file} =~ m{ |
|
486
|
|
|
|
|
|
|
\A |
|
487
|
|
|
|
|
|
|
(? |
|
488
|
|
|
|
|
|
|
(? (?: [^\n]* \n ){0,$prelim_lines}? (?&PerlOWS) ) |
|
489
|
|
|
|
|
|
|
(?> $SHOW_PATTERN ) |
|
490
|
|
|
|
|
|
|
) |
|
491
|
708
|
100
|
|
|
|
104946
|
(??{ ($+{showlines} =~ tr/\n//) >= $prelim_lines ? q{} : '(?!)' }) |
|
492
|
|
|
|
|
|
|
}xms; |
|
493
|
84
|
|
|
|
|
35343
|
my %cap = %+; |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
# Extract source code of call (the else should only very rarely need to be invoked)... |
|
496
|
84
|
50
|
|
|
|
593
|
if ($found) { |
|
497
|
84
|
|
|
|
|
1523
|
return @cap{qw}, 1 + ($+{prelines} =~ tr/\n//); |
|
498
|
|
|
|
|
|
|
} |
|
499
|
|
|
|
|
|
|
else { |
|
500
|
0
|
|
|
|
|
0
|
return q{}, [0, split /\n/, $source_cache{$file}]->[$line], q{}, $line; |
|
501
|
|
|
|
|
|
|
} |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
} |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# Attempt to detect a show() argument list that consists of a single hash... |
|
506
|
|
|
|
|
|
|
sub _data_is_single_hash { |
|
507
|
84
|
|
|
84
|
|
288
|
my ($context, $data_ref, $subname) = @_; |
|
508
|
84
|
|
50
|
|
|
264
|
$context //= q{}; |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
# What does a single hash arg look like??? |
|
511
|
84
|
|
|
|
|
145
|
state %SINGLE_HASH_FOR; |
|
512
|
84
|
|
66
|
|
|
345754
|
my $SINGLE_HASH = $SINGLE_HASH_FOR{$subname} |
|
513
|
|
|
|
|
|
|
//= qr{ \b(?:$subname) (?&PerlOWS) |
|
514
|
|
|
|
|
|
|
(?: (?&PerlVariableHash) |
|
515
|
|
|
|
|
|
|
| \( (?&PerlOWS) (?&PerlVariableHash) (?&PerlOWS) \) |
|
516
|
|
|
|
|
|
|
) |
|
517
|
|
|
|
|
|
|
(?&PerlOWS) \z |
|
518
|
|
|
|
|
|
|
$PPR::GRAMMAR |
|
519
|
|
|
|
|
|
|
}x; |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
# Must be only one argument (plus the invocant) and must look like a single hash... |
|
522
|
84
|
|
100
|
|
|
2101
|
return @{$data_ref} % 2 == 0 && $context =~ $SINGLE_HASH; |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# Base class for plugins... |
|
527
|
|
|
|
|
|
|
package Data::Show::Plugin; |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
# When imported, make the imported plugin a base class of the importing class... |
|
530
|
|
|
|
|
|
|
sub import { |
|
531
|
0
|
|
|
0
|
|
0
|
my ($package) = @_; |
|
532
|
11
|
|
|
11
|
|
82
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
19
|
|
|
|
11
|
|
|
|
|
12904
|
|
|
533
|
0
|
|
|
|
|
0
|
@{caller().'::ISA'} = $package; |
|
|
0
|
|
|
|
|
0
|
|
|
534
|
|
|
|
|
|
|
} |
|
535
|
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
# Visually distinguish the context string and data... |
|
537
|
|
|
|
|
|
|
sub format { |
|
538
|
70
|
|
|
70
|
0
|
332
|
my ($class, $file, $line, $pre_source, $source, $post_source, $data, $style, $termwidth) = @_; |
|
539
|
70
|
|
50
|
|
|
424
|
$_ //= q{} for $pre_source, $source, $post_source; |
|
540
|
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
# Track previous file context between calls... |
|
542
|
70
|
|
|
|
|
143
|
state $prevfile = q{}; |
|
543
|
70
|
|
|
|
|
173
|
my $is_new_context = $file ne $prevfile; |
|
544
|
70
|
|
|
|
|
155
|
$prevfile = $file; |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# Compute line numbering width... |
|
547
|
70
|
|
|
|
|
294
|
my $line_num_len = length( $line + ($pre_source . $source . $post_source) =~ tr/\n// ); |
|
548
|
70
|
|
|
|
|
235
|
my $data_box_len = $termwidth - 4; |
|
549
|
70
|
|
|
|
|
153
|
my $code_box_len = $data_box_len - $line_num_len - 1; |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# ASCII-only decoration if explicitly requested, or if Term::ANSIcolor is unavailable... |
|
552
|
|
|
|
|
|
|
my $decorate_data = $style->{mode} eq 'context' |
|
553
|
70
|
50
|
33
|
|
|
621
|
|| $style->{mode} eq 'off' ? \&_monochrome : \&_polychrome; |
|
554
|
70
|
50
|
|
|
|
259
|
my $decorate_context = $style->{mode} eq 'off' ? \&_monochrome : \&_polychrome; |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# Set up grid components (if requested)... |
|
557
|
70
|
|
|
|
|
283
|
my ($gridhead, $gridtail, $gridfsep, $gridcsep, $gridside, $gridline, $gridplus) = (q{}) x 7; |
|
558
|
70
|
|
|
|
|
200
|
my $padding = q{ } x $data_box_len; |
|
559
|
|
|
|
|
|
|
|
|
560
|
70
|
100
|
|
|
|
266
|
if ($style->{add_grid} ne 'off') { |
|
561
|
|
|
|
|
|
|
# $is_new_context !$is_new_context |
|
562
|
|
|
|
|
|
|
# Top line of grid: ┏━━━━━━━━━━━━━┓ ┏━━━┯━━━━━━━━━━┓ |
|
563
|
28
|
|
|
|
|
155
|
$gridhead = $GRID{'┏'} . ($GRID{'━'} x ($termwidth-2)) . $GRID{'┓'} . "\n"; |
|
564
|
28
|
100
|
|
|
|
172
|
substr($gridhead, $line_num_len+1, 1) = $GRID{'┯'} if !$is_new_context; |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
# Post-filename separator: ┠───┬─────────┨ |
|
567
|
28
|
|
|
|
|
125
|
$gridfsep = $GRID{'┠'} . ($GRID{'─'} x ($termwidth-2)) . $GRID{'┨'} . "\n"; |
|
568
|
28
|
|
|
|
|
84
|
substr($gridfsep, $line_num_len+1, 1) = $GRID{'┬'}; |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
# Post-code separator: ┠───┴─────────┨ ┠───┴─────────┨ |
|
571
|
28
|
|
|
|
|
93
|
$gridcsep = $GRID{'┠'} . ($GRID{'─'} x ($termwidth-2)) . $GRID{'┨'} . "\n"; |
|
572
|
28
|
|
|
|
|
91
|
substr($gridcsep, $line_num_len+1, 1) = $GRID{'┴'}; |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
# Bottom line of grid: ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ |
|
575
|
28
|
|
|
|
|
92
|
$gridtail = $GRID{'┗'} . ($GRID{'━'} x ($termwidth-2)) . $GRID{'┛'} . "\n"; |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
# Verticals of grid... |
|
578
|
28
|
|
|
|
|
58
|
$gridside = $GRID{'┃'}; |
|
579
|
28
|
|
|
|
|
59
|
$gridplus = $GRID{'┃'} . q{ }; |
|
580
|
28
|
|
|
|
|
66
|
$gridline = $GRID{'│'}; |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
# Decorate them all in the same style... |
|
583
|
28
|
|
|
|
|
84
|
for my $border ($gridhead, $gridtail, $gridfsep, $gridcsep, $gridside, $gridplus, $gridline) { |
|
584
|
196
|
|
|
|
|
440
|
$border = $decorate_context->($border, $style->{grid}); |
|
585
|
|
|
|
|
|
|
} |
|
586
|
|
|
|
|
|
|
} |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
# Style the source code... |
|
589
|
0
|
|
|
|
|
0
|
$source = join("\n", map {$decorate_context->($_, $style->{code})} split "\n", $pre_source) |
|
590
|
85
|
|
|
|
|
332
|
. join("\n", map {$decorate_context->($_, $style->{show})} split "\n", $source) |
|
591
|
70
|
|
|
|
|
450
|
. join("\n", map {$decorate_context->($_, $style->{code})} split "\n", $post_source); |
|
|
65
|
|
|
|
|
201
|
|
|
592
|
70
|
|
|
|
|
471
|
$source =~ s{ ^ }{ }gxms; |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# Install the line numbers and grid/format each source line... |
|
595
|
|
|
|
|
|
|
$source = join "\n", |
|
596
|
|
|
|
|
|
|
map { |
|
597
|
70
|
100
|
|
|
|
226
|
$style->{add_grid} eq 'off' |
|
|
85
|
|
|
|
|
5091
|
|
|
598
|
|
|
|
|
|
|
? m{ [^\n]* }xms |
|
599
|
|
|
|
|
|
|
: ($_ . $padding) =~ m{ $COLOUR_CHAR {$code_box_len} }xms; |
|
600
|
|
|
|
|
|
|
$gridside |
|
601
|
|
|
|
|
|
|
. $decorate_context->( sprintf('%*d', $line_num_len, $line++), $style->{line} ) |
|
602
|
85
|
|
|
|
|
585
|
. $gridline . q{ } . $& . q{ } . $gridside |
|
603
|
|
|
|
|
|
|
} |
|
604
|
|
|
|
|
|
|
split "\n", $source; |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# Trim, grid, and format each data line... |
|
607
|
|
|
|
|
|
|
$data = join "\n", |
|
608
|
|
|
|
|
|
|
map { |
|
609
|
70
|
100
|
|
|
|
325
|
$style->{add_grid} eq 'off' ? m{ [^\n]* }xms |
|
|
202
|
|
|
|
|
3643
|
|
|
610
|
|
|
|
|
|
|
: ($_ . $padding) =~ m{ $COLOUR_CHAR {$data_box_len} }xms; |
|
611
|
202
|
|
|
|
|
569
|
$gridplus . $decorate_data->( $&, $style->{data} ) . q{ } . $gridside |
|
612
|
|
|
|
|
|
|
} |
|
613
|
|
|
|
|
|
|
split "\n", $data; |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# Delineate source lines and data lines if no better styling has been specified... |
|
616
|
70
|
50
|
33
|
|
|
483
|
if (!$CAN_ANSICOLOR && $style->{add_grid} eq 'off') { |
|
617
|
0
|
|
|
|
|
0
|
$file =~ s{ ^ }{### }gxms; |
|
618
|
0
|
|
|
|
|
0
|
$source =~ s{ ^ }{### }gxms; |
|
619
|
0
|
|
|
|
|
0
|
$data =~ s{ ^ }{>>> }gxms; |
|
620
|
|
|
|
|
|
|
} |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
return $gridhead |
|
623
|
|
|
|
|
|
|
. ($is_new_context |
|
624
|
70
|
100
|
|
|
|
979
|
? do { |
|
625
|
5
|
100
|
|
|
|
139
|
$style->{add_grid} eq 'off' |
|
626
|
|
|
|
|
|
|
? $file =~ m{ [^\n]* }xms |
|
627
|
|
|
|
|
|
|
: ($file . $padding) =~ m{ $COLOUR_CHAR {$data_box_len} }xms; |
|
628
|
|
|
|
|
|
|
$gridplus |
|
629
|
|
|
|
|
|
|
. $decorate_context->( $&, $style->{file} ) |
|
630
|
5
|
|
|
|
|
23
|
. q{ } . $gridside . "\n" |
|
631
|
|
|
|
|
|
|
. $gridfsep |
|
632
|
|
|
|
|
|
|
} |
|
633
|
|
|
|
|
|
|
: q{} |
|
634
|
|
|
|
|
|
|
) |
|
635
|
|
|
|
|
|
|
. "$source\n" |
|
636
|
|
|
|
|
|
|
. $gridcsep |
|
637
|
|
|
|
|
|
|
. "$data\n" |
|
638
|
|
|
|
|
|
|
. $gridtail |
|
639
|
|
|
|
|
|
|
. "\n"; |
|
640
|
|
|
|
|
|
|
} |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
# Utility functions... |
|
644
|
|
|
|
|
|
|
|
|
645
|
638
|
|
|
638
|
|
2404
|
sub _monochrome { $_[0] } |
|
646
|
|
|
|
|
|
|
|
|
647
|
0
|
|
|
0
|
|
0
|
sub _polychrome { &Term::ANSIColor::colored } |
|
648
|
|
|
|
|
|
|
|
|
649
|
0
|
|
|
0
|
|
0
|
sub _antichrome { &Term::ANSIColor::colorstrip } |
|
650
|
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
sub _max { |
|
652
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
|
653
|
0
|
0
|
|
|
|
0
|
return $x > $y ? $x : $y; |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
# Convert the data to a printable form (using Data::Dumper)... |
|
657
|
|
|
|
|
|
|
sub stringify { |
|
658
|
56
|
|
|
56
|
0
|
173
|
my ($class, $data) = @_; |
|
659
|
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
# Choose conservative defaults (derive a subclass to change these)... |
|
661
|
11
|
|
|
11
|
|
5042
|
use Data::Dumper 'Dumper'; |
|
|
11
|
|
|
|
|
61516
|
|
|
|
11
|
|
|
|
|
1322
|
|
|
662
|
11
|
|
|
11
|
|
90
|
no warnings 'once'; |
|
|
11
|
|
|
|
|
23
|
|
|
|
11
|
|
|
|
|
2186
|
|
|
663
|
56
|
|
|
|
|
155
|
local $Data::Dumper::Deparse = 0; |
|
664
|
56
|
|
|
|
|
152
|
local $Data::Dumper::Sortkeys = 1; |
|
665
|
56
|
|
|
|
|
133
|
local $Data::Dumper::Deepcopy = 1; |
|
666
|
56
|
|
|
|
|
119
|
local $Data::Dumper::Terse = 1; |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# Convert data to a string representation... |
|
669
|
56
|
|
|
|
|
435
|
my $stringification = Dumper($data); |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
# Remove the annoying "$VAR1 = ", and realign subsequent indented lines... |
|
672
|
|
|
|
|
|
|
# $stringification =~ s{ ^ .{8} }{}gxms; |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
# Remove the annoying trailing ';'... |
|
675
|
56
|
|
|
|
|
5722
|
$stringification =~ s{ ; (\s*) \z }{$1}gxms; |
|
676
|
|
|
|
|
|
|
|
|
677
|
56
|
|
|
|
|
254
|
return $stringification; |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# Template for constructing standard plugins... |
|
682
|
11
|
|
|
11
|
|
4756
|
my $NULL_FORMATTER; BEGIN { $NULL_FORMATTER = q{ |
|
683
|
|
|
|
|
|
|
sub format { |
|
684
|
|
|
|
|
|
|
my ($class, $data) = @_; |
|
685
|
|
|
|
|
|
|
$data =~ s{\s*\z}{}; |
|
686
|
|
|
|
|
|
|
return "$data\n\n"; |
|
687
|
|
|
|
|
|
|
} |
|
688
|
|
|
|
|
|
|
}} |
|
689
|
|
|
|
|
|
|
sub _build_plugin { |
|
690
|
|
|
|
|
|
|
# (SHOUTY parameters get interpolated, mousy prarameters don't)... |
|
691
|
132
|
|
|
132
|
|
269
|
my ($NAME, $FALLBACK, $DUMP, $no_formatting) = @_; |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
# Handle non-formatting plugins, and optimize argument look-up... |
|
694
|
132
|
100
|
|
|
|
233
|
my $FORMATTER = $no_formatting ? $NULL_FORMATTER : q{}; |
|
695
|
132
|
|
|
|
|
159
|
my $DATA = '$_[1]'; |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
return |
|
698
|
132
|
|
|
|
|
1709
|
"Data::Show::Plugin::$NAME" => { |
|
699
|
|
|
|
|
|
|
requires => [$NAME], |
|
700
|
|
|
|
|
|
|
fallback => $FALLBACK, |
|
701
|
|
|
|
|
|
|
source => qq{ |
|
702
|
|
|
|
|
|
|
$FORMATTER |
|
703
|
|
|
|
|
|
|
sub stringify { |
|
704
|
|
|
|
|
|
|
use $NAME q{$DUMP}; |
|
705
|
|
|
|
|
|
|
return (eval { $DUMP($DATA) } |
|
706
|
|
|
|
|
|
|
// '<$NAME cannot show a ' . lc(ref($DATA)) . " reference>\n") |
|
707
|
|
|
|
|
|
|
. "\n"; |
|
708
|
|
|
|
|
|
|
} |
|
709
|
|
|
|
|
|
|
}, |
|
710
|
|
|
|
|
|
|
}; |
|
711
|
|
|
|
|
|
|
} |
|
712
|
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# Initialize the data needed to instantiate the built-in plugins on-demand... |
|
714
|
|
|
|
|
|
|
BEGIN { |
|
715
|
11
|
|
|
11
|
|
56
|
%STANDARD_PLUGIN = ( |
|
716
|
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
# DUMPER MODULE FALLBACK DUMP FUNC VARIATIONS |
|
718
|
|
|
|
|
|
|
_build_plugin( 'Data::Dmp' => 'Data::Pretty', 'dmp' ), |
|
719
|
|
|
|
|
|
|
_build_plugin( 'Data::Dump' => 'Data::Dumper', 'pp', ), |
|
720
|
|
|
|
|
|
|
_build_plugin( 'Data::Dump::Color' => 'Data::Dump', 'pp', 'preformatted' ), |
|
721
|
|
|
|
|
|
|
_build_plugin( 'Data::Dumper::Compact' => 'Data::Dump', 'ddc' ), |
|
722
|
|
|
|
|
|
|
_build_plugin( 'Data::Dumper::Concise' => 'Data::Dumper', 'Dumper' ), |
|
723
|
|
|
|
|
|
|
_build_plugin( 'Data::Dumper::Table' => 'Dumpvalue', 'Tabulate' ), |
|
724
|
|
|
|
|
|
|
_build_plugin( 'Data::Pretty' => 'Data::Dump', 'pp', ), |
|
725
|
|
|
|
|
|
|
_build_plugin( 'Data::TreeDumper' => 'Dumpvalue', 'DumpTree' ), |
|
726
|
|
|
|
|
|
|
_build_plugin( 'YAML' => 'YAML::Tiny', 'Dump' ), |
|
727
|
|
|
|
|
|
|
_build_plugin( 'YAML::PP' => 'YAML::Tiny', 'Dump' ), |
|
728
|
|
|
|
|
|
|
_build_plugin( 'YAML::Tiny' => 'Dumpvalue', 'Dump' ), |
|
729
|
|
|
|
|
|
|
_build_plugin( 'YAML::Tiny::Color' => 'YAML::Tiny', 'Dump', 'preformatted' ), |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
"Data::Show::Plugin::Data::Dumper" => { |
|
732
|
|
|
|
|
|
|
requires => ['Data::Dumper'], |
|
733
|
|
|
|
|
|
|
fallback => 'DumpValue', |
|
734
|
|
|
|
|
|
|
source => q{}, # ...because Data::Show::Plugin base class already uses Data::Dumper |
|
735
|
|
|
|
|
|
|
}, |
|
736
|
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
'Data::Show::Plugin::Data::Printer' => { |
|
738
|
|
|
|
|
|
|
requires => ['Data::Printer'], |
|
739
|
|
|
|
|
|
|
fallback => 'Dumpvalue', |
|
740
|
|
|
|
|
|
|
source => qq{ |
|
741
|
|
|
|
|
|
|
sub stringify { |
|
742
|
|
|
|
|
|
|
use Data::Printer; |
|
743
|
|
|
|
|
|
|
return np(\$_[1], colored=>$CAN_ANSICOLOR) . "\n"; |
|
744
|
|
|
|
|
|
|
} |
|
745
|
|
|
|
|
|
|
}, |
|
746
|
|
|
|
|
|
|
}, |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
'Data::Show::Plugin::Dumpvalue' => { |
|
749
|
|
|
|
|
|
|
requires => ['Dumpvalue'], |
|
750
|
|
|
|
|
|
|
source => q{ |
|
751
|
|
|
|
|
|
|
sub stringify { |
|
752
|
|
|
|
|
|
|
my ($class, $data) = @_; |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Create a singleton Dumpvalue object to do the stringification... |
|
755
|
|
|
|
|
|
|
use Dumpvalue; |
|
756
|
|
|
|
|
|
|
state $DUMPER = Dumpvalue->new(subdump=>1, globPrint=>1); |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
# Dumpvalue only dumps to STDOUT, so co-opt that filehandle to capture the output... |
|
759
|
|
|
|
|
|
|
open +(local *STDOUT), '>', \\my $dump; |
|
760
|
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
# Stringify the data to the captured STDOUT... |
|
762
|
|
|
|
|
|
|
$DUMPER->dumpValue($data); |
|
763
|
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
# Return the intercepted stringification... |
|
765
|
|
|
|
|
|
|
return $dump; |
|
766
|
|
|
|
|
|
|
} |
|
767
|
|
|
|
|
|
|
}, |
|
768
|
|
|
|
|
|
|
}, |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
# This plugin restores the previous (pre-version-0.003) output format for the module... |
|
771
|
|
|
|
|
|
|
'Data::Show::Plugin::Legacy' => { |
|
772
|
|
|
|
|
|
|
requires => ['Data::Dump', 'PPR', 'List::Util'], |
|
773
|
|
|
|
|
|
|
fallback => 'Data::Dump', |
|
774
|
|
|
|
|
|
|
source => q{ |
|
775
|
|
|
|
|
|
|
sub format { |
|
776
|
|
|
|
|
|
|
my ($class, $file, $line, |
|
777
|
|
|
|
|
|
|
$pre_source, $source, $post_source, |
|
778
|
|
|
|
|
|
|
$data, $style, $termwidth) = @_; |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
use List::Util 'max'; |
|
781
|
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
# Configuration for layout of representation... |
|
783
|
|
|
|
|
|
|
state $DEFAULT_INDENT = 4; |
|
784
|
|
|
|
|
|
|
state $MAX_DESC = 30; |
|
785
|
|
|
|
|
|
|
state $MAX_FILENAME = 20; |
|
786
|
|
|
|
|
|
|
state $TITLE_POS = 3; |
|
787
|
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
# Extract description of arglist from source... |
|
789
|
|
|
|
|
|
|
$source =~ s{\\A show \b \\s*}{}x; |
|
790
|
|
|
|
|
|
|
$source =~ s{\\s+}{ }gx; |
|
791
|
|
|
|
|
|
|
$source =~ s{\\A \\( (.*) \\) \\Z}{$1}x; |
|
792
|
|
|
|
|
|
|
if (length($source) > $MAX_DESC) { |
|
793
|
|
|
|
|
|
|
$source = substr($source,0,$MAX_DESC-3) . q{...}; |
|
794
|
|
|
|
|
|
|
} |
|
795
|
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
# Trim filename and format context info and description... |
|
797
|
|
|
|
|
|
|
$file =~ s{.*[/\\\\]}{}xms; |
|
798
|
|
|
|
|
|
|
if (length($file) > $MAX_FILENAME) { |
|
799
|
|
|
|
|
|
|
$file =~ s/ (_[^\\W_]) [^\\W_]* /$1/gxms; |
|
800
|
|
|
|
|
|
|
} |
|
801
|
|
|
|
|
|
|
if (length($file) > $MAX_FILENAME) { |
|
802
|
|
|
|
|
|
|
$file =~ s/\\A (.{1,8}) .*? (.{1,8}) \\Z/$1...$2/gxms; |
|
803
|
|
|
|
|
|
|
} |
|
804
|
|
|
|
|
|
|
my $context = "[ '$file', line $line ]"; |
|
805
|
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# Insert title into header... |
|
807
|
|
|
|
|
|
|
my $header = '=' x $termwidth; |
|
808
|
|
|
|
|
|
|
substr($header, $TITLE_POS, length($source)+6) = "( $source )"; |
|
809
|
|
|
|
|
|
|
substr($header, -(length($context)+$TITLE_POS), length($context)) = $context; |
|
810
|
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# Indent data... |
|
812
|
|
|
|
|
|
|
$data =~ s{^}{ }gxms; |
|
813
|
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
# Assemble and send off... |
|
815
|
|
|
|
|
|
|
return "$header\\n\\n$data\\n\\n"; |
|
816
|
|
|
|
|
|
|
} |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
# Original stringifier was Data::Dump... |
|
819
|
|
|
|
|
|
|
sub stringify { |
|
820
|
|
|
|
|
|
|
use Data::Dump 'pp'; |
|
821
|
|
|
|
|
|
|
return pp($_[1]); |
|
822
|
|
|
|
|
|
|
} |
|
823
|
|
|
|
|
|
|
}, |
|
824
|
|
|
|
|
|
|
}, |
|
825
|
|
|
|
|
|
|
); |
|
826
|
|
|
|
|
|
|
} |
|
827
|
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
|
830
|
|
|
|
|
|
|
__END__ |