line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RapidApp::Util; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Misc util and sugar functions for RapidApp |
4
|
|
|
|
|
|
|
|
5
|
6
|
|
|
6
|
|
116746
|
use strict; |
|
6
|
|
|
|
|
28
|
|
|
6
|
|
|
|
|
150
|
|
6
|
6
|
|
|
6
|
|
26
|
use warnings; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
152
|
|
7
|
|
|
|
|
|
|
|
8
|
6
|
|
|
6
|
|
27
|
use Scalar::Util qw(blessed weaken reftype); |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
319
|
|
9
|
6
|
|
|
6
|
|
2204
|
use Clone qw(clone); |
|
6
|
|
|
|
|
12582
|
|
|
6
|
|
|
|
|
294
|
|
10
|
6
|
|
|
6
|
|
38
|
use Carp qw(carp croak confess cluck longmess shortmess); |
|
6
|
|
|
|
|
9
|
|
|
6
|
|
|
|
|
311
|
|
11
|
6
|
|
|
6
|
|
1523
|
use Try::Tiny; |
|
6
|
|
|
|
|
4397
|
|
|
6
|
|
|
|
|
275
|
|
12
|
6
|
|
|
6
|
|
1672
|
use Time::HiRes qw(gettimeofday tv_interval); |
|
6
|
|
|
|
|
4266
|
|
|
6
|
|
|
|
|
28
|
|
13
|
6
|
|
|
6
|
|
3216
|
use Data::Dumper::Concise qw(Dumper); |
|
6
|
|
|
|
|
25370
|
|
|
6
|
|
|
|
|
399
|
|
14
|
6
|
|
|
6
|
|
3020
|
use Term::ANSIColor qw(:constants); |
|
6
|
|
|
|
|
41986
|
|
|
6
|
|
|
|
|
6315
|
|
15
|
6
|
|
|
|
|
516
|
use RapidApp::JSON::MixedEncoder qw( |
16
|
|
|
|
|
|
|
encode_json decode_json encode_json_utf8 decode_json_utf8 encode_json_ascii decode_json_ascii |
17
|
6
|
|
|
6
|
|
2604
|
); |
|
6
|
|
|
|
|
14
|
|
18
|
|
|
|
|
|
|
|
19
|
6
|
|
|
6
|
|
2774
|
use RapidApp::Util::Hash::Merge qw( merge ); |
|
6
|
|
|
|
|
13
|
|
|
6
|
|
|
|
|
396
|
|
20
|
|
|
|
|
|
|
RapidApp::Util::Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' ); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
BEGIN { |
24
|
6
|
|
|
6
|
|
39
|
use Exporter; |
|
6
|
|
|
|
|
9
|
|
|
6
|
|
|
|
|
199
|
|
25
|
6
|
|
|
6
|
|
395
|
use parent 'Exporter'; |
|
6
|
|
|
|
|
252
|
|
|
6
|
|
|
|
|
40
|
|
26
|
|
|
|
|
|
|
|
27
|
6
|
|
|
6
|
|
297
|
use vars qw (@EXPORT_OK %EXPORT_TAGS); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
550
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# These are *extra* exports which came to us via other packages. Note that |
30
|
|
|
|
|
|
|
# all functions defined directly in the class will also be added to the |
31
|
|
|
|
|
|
|
# @EXPORT_OK and setup with the :all tag (see the end of the file) |
32
|
6
|
|
|
6
|
|
51
|
@EXPORT_OK = qw( |
33
|
|
|
|
|
|
|
blessed weaken reftype |
34
|
|
|
|
|
|
|
clone |
35
|
|
|
|
|
|
|
carp croak confess cluck longmess shortmess |
36
|
|
|
|
|
|
|
try catch finally |
37
|
|
|
|
|
|
|
gettimeofday tv_interval |
38
|
|
|
|
|
|
|
Dumper |
39
|
|
|
|
|
|
|
encode_json decode_json encode_json_utf8 decode_json_utf8 encode_json_ascii decode_json_ascii |
40
|
|
|
|
|
|
|
merge |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
6
|
|
|
|
|
8
|
push @EXPORT_OK, @{$Term::ANSIColor::EXPORT_TAGS{constants}}; |
|
6
|
|
|
|
|
74
|
|
44
|
|
|
|
|
|
|
|
45
|
6
|
|
|
|
|
138
|
%EXPORT_TAGS = ( |
46
|
|
|
|
|
|
|
all => \@EXPORT_OK |
47
|
|
|
|
|
|
|
); |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
6
|
|
|
6
|
|
2211
|
use RapidApp::Responder::UserError; |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
226
|
|
51
|
6
|
|
|
6
|
|
3095
|
use RapidApp::Responder::CustomPrompt; |
|
6
|
|
|
|
|
20
|
|
|
6
|
|
|
|
|
292
|
|
52
|
6
|
|
|
6
|
|
3054
|
use RapidApp::Responder::InfoStatus; |
|
6
|
|
|
|
|
19
|
|
|
6
|
|
|
|
|
205
|
|
53
|
6
|
|
|
6
|
|
2939
|
use RapidApp::JSONFunc; |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
197
|
|
54
|
6
|
|
|
6
|
|
41
|
use RapidApp::JSON::MixedEncoder; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
341
|
|
55
|
6
|
|
|
6
|
|
2953
|
use RapidApp::JSON::RawJavascript; |
|
6
|
|
|
|
|
18
|
|
|
6
|
|
|
|
|
192
|
|
56
|
6
|
|
|
6
|
|
2774
|
use RapidApp::JSON::ScriptWithData; |
|
6
|
|
|
|
|
15
|
|
|
6
|
|
|
|
|
156
|
|
57
|
|
|
|
|
|
|
|
58
|
6
|
|
|
6
|
|
2039
|
use RapidApp::HTML::RawHtml; |
|
6
|
|
|
|
|
18
|
|
|
6
|
|
|
|
|
182
|
|
59
|
6
|
|
|
6
|
|
1968
|
use RapidApp::Handler; |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
248
|
|
60
|
6
|
|
|
6
|
|
47
|
use HTML::Entities; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
370
|
|
61
|
6
|
|
|
6
|
|
2881
|
use RapidApp::RootModule; |
|
6
|
|
|
|
|
23
|
|
|
6
|
|
|
|
|
551
|
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
######################################################################## |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub scream { |
67
|
0
|
|
|
0
|
0
|
0
|
local $_ = caller_data(3); |
68
|
0
|
|
|
|
|
0
|
scream_color(YELLOW . BOLD,@_); |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub scream_color { |
72
|
0
|
|
|
0
|
0
|
0
|
my $color = shift; |
73
|
6
|
|
|
6
|
|
52
|
no warnings 'uninitialized'; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
14324
|
|
74
|
|
|
|
|
|
|
|
75
|
0
|
|
0
|
|
|
0
|
my $maxdepth = $Data::Dumper::Maxdepth || 4; |
76
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Maxdepth = $maxdepth; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
local $_ = caller_data(3) unless ( |
79
|
|
|
|
|
|
|
$_ eq 'no_caller_data' or ( |
80
|
|
|
|
|
|
|
ref($_) eq 'ARRAY' and |
81
|
|
|
|
|
|
|
scalar(@$_) == 3 and |
82
|
|
|
|
|
|
|
ref($_->[0]) eq 'HASH' and |
83
|
|
|
|
|
|
|
defined $_->[0]->{package} |
84
|
|
|
|
|
|
|
) |
85
|
0
|
0
|
0
|
|
|
0
|
); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
0
|
my $data = $_[0]; |
88
|
0
|
0
|
|
|
|
0
|
$data = \@_ if (scalar(@_) > 1); |
89
|
0
|
0
|
|
|
|
0
|
$data = Dumper($data) if (ref $data); |
90
|
0
|
0
|
|
|
|
0
|
$data = ' ' . UNDERLINE . 'undef' unless (defined $data); |
91
|
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
0
|
my $pre = ''; |
93
|
|
|
|
|
|
|
$pre = BOLD . ($_->[2]->{subroutine} ? $_->[2]->{subroutine} . ' ' : '') . |
94
|
0
|
0
|
|
|
|
0
|
'[line ' . $_->[1]->{line} . ']: ' . CLEAR . "\n" unless ($_ eq 'no_caller_data'); |
|
|
0
|
|
|
|
|
|
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
0
|
print STDERR $pre . $color . $data . CLEAR . "\n"; |
97
|
|
|
|
|
|
|
|
98
|
0
|
|
|
|
|
0
|
return @_; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Takes a list and returns a HashRef. List can be a mixed Hash/List: |
103
|
|
|
|
|
|
|
#( |
104
|
|
|
|
|
|
|
# item1 => { opt1 => 'foo' }, |
105
|
|
|
|
|
|
|
# item2 => { key => 'data', foo => 'blah' }, |
106
|
|
|
|
|
|
|
# 'item3', |
107
|
|
|
|
|
|
|
# 'item4', |
108
|
|
|
|
|
|
|
# item1 => { opt2 => 'foobar', opt3 => 'zippy do da' } |
109
|
|
|
|
|
|
|
#) |
110
|
|
|
|
|
|
|
# Bare items like item3 and item4 become {} in the returned hashref. |
111
|
|
|
|
|
|
|
# Repeated items like item1 and merged |
112
|
|
|
|
|
|
|
# also handles the first arg as a hashref or arrayref |
113
|
|
|
|
|
|
|
sub get_mixed_hash_args { |
114
|
0
|
|
|
0
|
0
|
0
|
my @args = @_; |
115
|
0
|
0
|
|
|
|
0
|
return $args[0] if (ref($args[0]) eq 'HASH'); |
116
|
0
|
0
|
|
|
|
0
|
@args = @{ $args[0] } if (ref($args[0]) eq 'ARRAY'); |
|
0
|
|
|
|
|
0
|
|
117
|
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
0
|
my $hashref = {}; |
119
|
0
|
|
|
|
|
0
|
my $last; |
120
|
0
|
|
|
|
|
0
|
foreach my $item (@args) { |
121
|
0
|
0
|
|
|
|
0
|
if (ref($item)) { |
122
|
0
|
0
|
0
|
|
|
0
|
die "Error in arguments" unless (ref($item) eq 'HASH' and defined $last and not ref($last)); |
|
|
|
0
|
|
|
|
|
123
|
0
|
|
|
|
|
0
|
$hashref->{$last} = { %{$hashref->{$last}}, %$item }; |
|
0
|
|
|
|
|
0
|
|
124
|
0
|
|
|
|
|
0
|
next; |
125
|
|
|
|
|
|
|
} |
126
|
0
|
|
|
|
|
0
|
$last = $item; |
127
|
0
|
0
|
|
|
|
0
|
$hashref->{$item} = {} unless (defined $hashref->{$item}); |
128
|
|
|
|
|
|
|
} |
129
|
0
|
|
|
|
|
0
|
return $hashref; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# Takes a list and returns a Hash. Like get_mixed_hash_args, but |
134
|
|
|
|
|
|
|
# list order is preserved |
135
|
|
|
|
|
|
|
sub get_mixed_hash_args_ordered { |
136
|
0
|
|
|
0
|
0
|
0
|
my @args = @_; |
137
|
0
|
0
|
|
|
|
0
|
return $args[0] if (ref($args[0]) eq 'HASH'); |
138
|
0
|
0
|
|
|
|
0
|
@args = @{ $args[0] } if (ref($args[0]) eq 'ARRAY'); |
|
0
|
|
|
|
|
0
|
|
139
|
|
|
|
|
|
|
|
140
|
0
|
|
|
|
|
0
|
my $hashref = {}; |
141
|
0
|
|
|
|
|
0
|
my @list = (); |
142
|
0
|
|
|
|
|
0
|
my $last; |
143
|
0
|
|
|
|
|
0
|
foreach my $item (@args) { |
144
|
0
|
0
|
|
|
|
0
|
if (ref($item)) { |
145
|
0
|
0
|
0
|
|
|
0
|
die "Error in arguments" unless (ref($item) eq 'HASH' and defined $last and not ref($last)); |
|
|
|
0
|
|
|
|
|
146
|
0
|
|
|
|
|
0
|
$hashref->{$last} = { %{$hashref->{$last}}, %$item }; |
|
0
|
|
|
|
|
0
|
|
147
|
0
|
|
|
|
|
0
|
push @list, $last, $hashref->{$last}; |
148
|
0
|
|
|
|
|
0
|
next; |
149
|
|
|
|
|
|
|
} |
150
|
0
|
0
|
|
|
|
0
|
$hashref->{$item} = {} unless (defined $hashref->{$item}); |
151
|
0
|
0
|
|
|
|
0
|
push @list,$item,$hashref->{$item} unless (ref $last); |
152
|
0
|
|
|
|
|
0
|
$last = $item; |
153
|
|
|
|
|
|
|
} |
154
|
0
|
|
|
|
|
0
|
return @list; # <-- preserve order |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# returns \0 and \1 as 0 and 1, and returns 0 and 1 as 0 and 1 |
159
|
|
|
|
|
|
|
sub jstrue { |
160
|
2970
|
|
|
2970
|
0
|
5293
|
my $v = shift; |
161
|
2970
|
100
|
66
|
|
|
16337
|
ref($v) && ref($v) eq 'SCALAR' ? $$v : $v; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# The coderefs supplied here get called immediately after the |
166
|
|
|
|
|
|
|
# _load_root_module method in RapidApp/RapidApp.pm |
167
|
|
|
|
|
|
|
sub rapidapp_add_global_init_coderef { |
168
|
0
|
|
|
0
|
0
|
0
|
foreach my $ref (@_) { |
169
|
0
|
0
|
|
|
|
0
|
ref($ref) eq 'CODE' or die "rapidapp_add_global_init_coderef: argument is not a CodeRef: " . Dumper($ref); |
170
|
0
|
|
|
|
|
0
|
push @RapidApp::RootModule::GLOBAL_INIT_CODEREFS, $ref; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Returns an arrayref of hashes containing standard 'caller' function data |
175
|
|
|
|
|
|
|
# with named properties: |
176
|
|
|
|
|
|
|
sub caller_data { |
177
|
0
|
|
0
|
0
|
0
|
0
|
my $depth = shift || 1; |
178
|
|
|
|
|
|
|
|
179
|
0
|
|
|
|
|
0
|
my @list = (); |
180
|
0
|
|
|
|
|
0
|
for(my $i = 0; $i < $depth; $i++) { |
181
|
0
|
|
|
|
|
0
|
my $h = {}; |
182
|
|
|
|
|
|
|
($h->{package}, $h->{filename}, $h->{line}, $h->{subroutine}, $h->{hasargs}, |
183
|
0
|
|
|
|
|
0
|
$h->{wantarray}, $h->{evaltext}, $h->{is_require}, $h->{hints}, $h->{bitmask}) = caller($i); |
184
|
0
|
0
|
|
|
|
0
|
push @list,$h if($h->{package}); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
0
|
return \@list; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub caller_data_brief { |
191
|
0
|
|
0
|
0
|
0
|
0
|
my $depth = shift || 1; |
192
|
0
|
|
|
|
|
0
|
my $list = caller_data($depth + 1); |
193
|
0
|
|
|
|
|
0
|
my $regex = shift; |
194
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
0
|
shift @$list; |
196
|
0
|
|
|
|
|
0
|
shift @$list; |
197
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
0
|
my @inc_parms = qw(subroutine line filename); |
199
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
0
|
my %inc = map { $_ => 1 } @inc_parms; |
|
0
|
|
|
|
|
0
|
|
201
|
|
|
|
|
|
|
|
202
|
0
|
|
|
|
|
0
|
my @new = (); |
203
|
0
|
|
|
|
|
0
|
my $seq = 0; |
204
|
0
|
|
|
|
|
0
|
foreach my $item (@$list) { |
205
|
0
|
0
|
0
|
|
|
0
|
if($regex and ! eval('$item->{subroutine} =~ /' . $regex . '/')) { |
206
|
0
|
|
|
|
|
0
|
$seq++; |
207
|
0
|
|
|
|
|
0
|
next; |
208
|
|
|
|
|
|
|
} |
209
|
0
|
0
|
|
|
|
0
|
push @new, ' . ' x $seq if ($seq); |
210
|
0
|
|
|
|
|
0
|
$seq = 0; |
211
|
0
|
|
|
|
|
0
|
push @new, { map { $_ => $item->{$_} } grep { $inc{$_} } keys %$item }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
return \@new; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# TODO: replace this with uniq from List::Utils |
219
|
|
|
|
|
|
|
# Returns a list with duplicates removed. If passed a single arrayref, duplicates are |
220
|
|
|
|
|
|
|
# removed from the arrayref in place, and the new list (contents) are returned. |
221
|
|
|
|
|
|
|
sub uniq { |
222
|
10078
|
|
|
10078
|
0
|
15276
|
my %seen = (); |
223
|
10078
|
50
|
100
|
|
|
31616
|
return grep { !$seen{ defined $_ ? $_ : '___!undef!___'}++ } @_ unless (@_ == 1 and ref($_[0]) eq 'ARRAY'); |
|
23797
|
100
|
|
|
|
121790
|
|
224
|
184
|
50
|
|
|
|
339
|
return () unless (@{$_[0]} > 0); |
|
184
|
|
|
|
|
552
|
|
225
|
|
|
|
|
|
|
# we add the first element to the end of the arg list to prevetn deep recursion in the |
226
|
|
|
|
|
|
|
# case of nested single element arrayrefs |
227
|
184
|
|
|
|
|
333
|
@{$_[0]} = uniq(@{$_[0]},$_[0]->[0]); |
|
184
|
|
|
|
|
511
|
|
|
184
|
|
|
|
|
719
|
|
228
|
184
|
|
|
|
|
268
|
return @{$_[0]}; |
|
184
|
|
|
|
|
412
|
|
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
sub deref { |
232
|
0
|
|
|
0
|
0
|
0
|
my $ref = shift; |
233
|
0
|
|
0
|
|
|
0
|
my $type = ref $ref || return $ref,@_; |
234
|
0
|
0
|
|
|
|
0
|
die 'deref(): more than 1 argument not supported' if (@_ > 0); |
235
|
0
|
0
|
|
|
|
0
|
return $$ref if ($type eq 'SCALAR'); |
236
|
0
|
0
|
|
|
|
0
|
return @$ref if ($type eq 'ARRAY'); |
237
|
0
|
0
|
|
|
|
0
|
return %$ref if ($type eq 'HASH'); |
238
|
0
|
|
|
|
|
0
|
die "deref(): invalid ref type '$type' - supported types: SCALAR, ARRAY and HASH"; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Generic function returns a short display string of a supplied value/values |
242
|
|
|
|
|
|
|
# This is like a lite version of Dumper meant more for single values |
243
|
|
|
|
|
|
|
# Accepts optional CodeRef as first argument for custom handling, for example, |
244
|
|
|
|
|
|
|
# this would allow you to use Dumper instead for all ref values: |
245
|
|
|
|
|
|
|
# print disp(sub{ ref $_ ? Dumper($_) : undef },$_) for (@vals); |
246
|
|
|
|
|
|
|
sub disp { |
247
|
0
|
0
|
|
0
|
0
|
0
|
my $recurse = (caller(1))[3] eq __PACKAGE__ . '::disp' ? 1 : 0; #<-- true if called by ourself |
248
|
|
|
|
|
|
|
|
249
|
0
|
0
|
|
|
|
0
|
local $_{code} = $recurse ? $_{code} : undef; |
250
|
0
|
0
|
0
|
|
|
0
|
$_{code} = shift if(ref($_[0]) eq 'CODE' && @_>1 && $recurse == 0); |
|
|
|
0
|
|
|
|
|
251
|
0
|
0
|
|
|
|
0
|
if($_{code}) { |
252
|
0
|
|
|
|
|
0
|
local $_ = $_[0]; |
253
|
0
|
|
|
|
|
0
|
my $cust = $_{code}->(@_); |
254
|
0
|
0
|
|
|
|
0
|
return $cust if (defined $cust); |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
0
|
0
|
|
|
|
0
|
return join(',',map {disp($_)} @_) if(@_>1); |
|
0
|
|
|
|
|
0
|
|
258
|
0
|
|
|
|
|
0
|
my $val = shift; |
259
|
0
|
0
|
|
|
|
0
|
return 'undef' unless (defined $val); |
260
|
0
|
0
|
|
|
|
0
|
if(ref $val) { |
261
|
0
|
0
|
|
|
|
0
|
return '[' . disp(@$val) . ']' if (ref($val) eq 'ARRAY'); |
262
|
0
|
0
|
|
|
|
0
|
return '\\' . disp($$val) if (ref($val) eq 'SCALAR'); |
263
|
0
|
0
|
|
|
|
0
|
return '{ ' . join(',',map { $_ . ' => ' . disp($val->{$_}) } keys %$val) . ' }' if (ref($val) eq 'HASH'); |
|
0
|
|
|
|
|
0
|
|
264
|
0
|
|
|
|
|
0
|
return "$val" #<-- generic fall-back for other references |
265
|
|
|
|
|
|
|
} |
266
|
0
|
|
|
|
|
0
|
return "'" . $val . "'"; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub print_trunc($$) { |
271
|
6
|
|
|
6
|
0
|
10
|
my $max_length = shift; |
272
|
6
|
|
|
|
|
11
|
my $str = shift; |
273
|
|
|
|
|
|
|
|
274
|
6
|
50
|
33
|
|
|
56
|
die "Invalid max length '$max_length'" unless ( |
|
|
|
33
|
|
|
|
|
275
|
|
|
|
|
|
|
defined $max_length && |
276
|
|
|
|
|
|
|
$max_length =~ /^\d+$/ && |
277
|
|
|
|
|
|
|
$max_length > 0 |
278
|
|
|
|
|
|
|
); |
279
|
|
|
|
|
|
|
|
280
|
6
|
100
|
|
|
|
19
|
return 'undef' unless (defined $str); |
281
|
3
|
50
|
|
|
|
10
|
if (ref $str) { |
282
|
0
|
|
|
|
|
0
|
$str = disp($str); |
283
|
0
|
|
|
|
|
0
|
$str =~ s/^\'//; |
284
|
0
|
|
|
|
|
0
|
$str =~ s/\'$//; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# escape single quotes: |
288
|
3
|
|
|
|
|
9
|
$str =~ s/'/\\'/g; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# convert tabs: |
291
|
3
|
|
|
|
|
7
|
$str =~ s/\t/ /g; |
292
|
|
|
|
|
|
|
|
293
|
3
|
|
|
|
|
7
|
my $length = length $str; |
294
|
3
|
50
|
|
|
|
25
|
return "'" . $str . "'" if ($length <= $max_length); |
295
|
0
|
|
|
|
|
0
|
return "'" . substr($str,0,$max_length) . "'...<$length" . " bytes> "; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
our $debug_arounds_set = {}; |
299
|
|
|
|
|
|
|
our $debug_around_nest_level = 0; |
300
|
|
|
|
|
|
|
our $debug_around_last_nest_level = 0; |
301
|
|
|
|
|
|
|
our $debug_around_stats = {}; |
302
|
|
|
|
|
|
|
our $debug_around_nest_elapse = 0; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
sub debug_around($@) { |
305
|
0
|
|
|
0
|
0
|
0
|
my ($pkg,$filename,$line) = caller; |
306
|
0
|
|
|
|
|
0
|
my $method = shift; |
307
|
0
|
|
|
|
|
0
|
my @methods = ( $method ); |
308
|
0
|
0
|
|
|
|
0
|
@methods = @$method if (ref($method) eq 'ARRAY'); |
309
|
|
|
|
|
|
|
|
310
|
0
|
0
|
|
|
|
0
|
my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref |
|
0
|
|
|
|
|
0
|
|
311
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
0
|
%opt = ( |
313
|
|
|
|
|
|
|
pkg => $pkg, |
314
|
|
|
|
|
|
|
filename => $filename, |
315
|
|
|
|
|
|
|
line => $line, |
316
|
|
|
|
|
|
|
%opt |
317
|
|
|
|
|
|
|
); |
318
|
|
|
|
|
|
|
|
319
|
0
|
|
|
|
|
0
|
$pkg = $opt{pkg}; |
320
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
0
|
foreach my $method (@methods) { |
322
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
0
|
my $package = $pkg; |
324
|
0
|
|
|
|
|
0
|
my @namespace = split(/::/,$method); |
325
|
0
|
0
|
|
|
|
0
|
if(scalar @namespace > 1) { |
326
|
0
|
|
|
|
|
0
|
$method = pop @namespace; |
327
|
0
|
|
|
|
|
0
|
$package = join('::',@namespace); |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
0
|
0
|
|
|
|
0
|
next if ($debug_arounds_set->{$package . '->' . $method}++); #<-- if its already set |
331
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
0
|
eval "require $package;"; |
333
|
0
|
|
|
|
|
0
|
my $around = func_debug_around($method, %opt, pkg => $package); |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# It's a Moose class or otherwise already has an 'around' class method: |
336
|
0
|
0
|
|
|
|
0
|
if($package->can('around')) { |
337
|
0
|
|
|
|
|
0
|
$package->can('around')->($method => $around); |
338
|
0
|
|
|
|
|
0
|
next; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# The class doesn't have an around method, so we'll setup manually with Class::MOP: |
342
|
0
|
|
|
|
|
0
|
my $meta = Class::MOP::Class->initialize($package); |
343
|
0
|
|
|
|
|
0
|
$meta->add_around_method_modifier($method => $around) |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Returns a coderef - designed to be a Moose around modifier - that will |
348
|
|
|
|
|
|
|
# print useful debug info about the given function to which it is attached |
349
|
|
|
|
|
|
|
sub func_debug_around { |
350
|
0
|
|
|
0
|
0
|
0
|
my $name = shift; |
351
|
0
|
0
|
|
|
|
0
|
my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref |
|
0
|
|
|
|
|
0
|
|
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
%opt = ( |
354
|
|
|
|
|
|
|
track_stats => 1, |
355
|
|
|
|
|
|
|
time => 1, |
356
|
|
|
|
|
|
|
verbose => 0, |
357
|
|
|
|
|
|
|
verbose_in => undef, |
358
|
|
|
|
|
|
|
verbose_out => undef, |
359
|
|
|
|
|
|
|
newline => 0, |
360
|
|
|
|
|
|
|
list_args => 0, |
361
|
|
|
|
|
|
|
list_out => 0, |
362
|
|
|
|
|
|
|
dump_maxdepth => 3, |
363
|
|
|
|
|
|
|
use_json => 0, |
364
|
|
|
|
|
|
|
stack => 0, |
365
|
|
|
|
|
|
|
instance => 0, |
366
|
|
|
|
|
|
|
color => GREEN, |
367
|
|
|
|
|
|
|
ret_color => RED.BOLD, |
368
|
0
|
|
|
0
|
|
0
|
arg_ignore => sub { 0 }, # <-- no debug output prited when this returns true |
369
|
0
|
|
|
0
|
|
0
|
return_ignore => sub { 0 },# <-- no debug output prited when this returns true |
370
|
0
|
|
|
|
|
0
|
%opt |
371
|
|
|
|
|
|
|
); |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# around wrapper in %opt to allow the user to pass a different one to use: |
374
|
|
|
|
|
|
|
$opt{around} ||= sub { |
375
|
0
|
|
|
0
|
|
0
|
my $orig = shift; |
376
|
0
|
|
|
|
|
0
|
my $self = shift; |
377
|
0
|
0
|
|
|
|
0
|
print STDERR "\n" if ($opt{newline}); |
378
|
0
|
|
|
|
|
0
|
return $self->$orig(@_); |
379
|
0
|
|
0
|
|
|
0
|
}; |
380
|
|
|
|
|
|
|
|
381
|
0
|
0
|
0
|
|
|
0
|
$opt{verbose_in} = 1 if ($opt{verbose} and not defined $opt{verbose_in}); |
382
|
0
|
0
|
0
|
|
|
0
|
$opt{verbose_out} = 1 if ($opt{verbose} and not defined $opt{verbose_out}); |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
$opt{dump_func} = sub { |
385
|
0
|
|
|
0
|
|
0
|
my $verbose = shift; |
386
|
0
|
0
|
0
|
|
|
0
|
return UNDERLINE . 'undef' . CLEAR unless (@_ > 0 and defined $_[0]); |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
# if list_out is false, return the number of items in the return, underlined |
389
|
0
|
0
|
|
|
|
0
|
return $opt{list_out} ? join(',',map { ref $_ ? "$_" : "'$_'" } @_) : UNDERLINE . @_ . CLEAR |
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
|
390
|
|
|
|
|
|
|
unless ($verbose); |
391
|
|
|
|
|
|
|
|
392
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Maxdepth = $opt{dump_maxdepth}; |
393
|
0
|
0
|
|
|
|
0
|
return Dumper(@_) unless ($opt{use_json}); |
394
|
|
|
|
|
|
|
#return RapidApp::JSON::MixedEncoder->new->allow_blessed->convert_blessed->allow_nonref->encode(\@_); |
395
|
0
|
|
|
|
|
0
|
return encode_json(\@_); |
396
|
0
|
0
|
|
|
|
0
|
} unless ($opt{dump_func}); |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
return sub { |
399
|
0
|
|
|
0
|
|
0
|
my $orig = shift; |
400
|
0
|
|
|
|
|
0
|
my $self = shift; |
401
|
0
|
|
|
|
|
0
|
my @args = @_; |
402
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
0
|
my $nest_level = $debug_around_nest_level; |
404
|
0
|
|
|
|
|
0
|
local $debug_around_nest_level = $debug_around_nest_level + 1; |
405
|
|
|
|
|
|
|
|
406
|
0
|
0
|
|
|
|
0
|
my $new_nest = $debug_around_last_nest_level < $nest_level ? 1 : 0; |
407
|
0
|
0
|
|
|
|
0
|
my $leave_nest = $debug_around_last_nest_level > $nest_level ? 1 : 0; |
408
|
0
|
|
|
|
|
0
|
$debug_around_last_nest_level = $nest_level; |
409
|
|
|
|
|
|
|
|
410
|
0
|
0
|
|
|
|
0
|
$debug_around_nest_elapse = 0 if ($nest_level == 0); |
411
|
|
|
|
|
|
|
|
412
|
0
|
0
|
|
|
|
0
|
my $indent = $nest_level > 0 ? (' ' x $nest_level) : ''; |
413
|
0
|
|
|
|
|
0
|
my $newline = "\n$indent"; |
414
|
|
|
|
|
|
|
|
415
|
0
|
|
|
|
|
0
|
my $has_refs = 0; |
416
|
|
|
|
|
|
|
|
417
|
0
|
|
|
|
|
0
|
my $in = '(' . UNDERLINE . @args . CLEAR . '): '; |
418
|
0
|
0
|
|
|
|
0
|
if($opt{list_args}) { |
419
|
0
|
0
|
0
|
|
|
0
|
my @print_args = map { (ref($_) and ++$has_refs) ? "$_" : MAGENTA . "'$_'" . CLEAR } @args; |
|
0
|
|
|
|
|
0
|
|
420
|
0
|
|
|
|
|
0
|
$in = '(' . join(',',@print_args) . '): '; |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
0
|
|
|
|
|
0
|
my $class = $opt{pkg}; |
424
|
0
|
0
|
|
|
|
0
|
if($opt{stack}) { |
425
|
0
|
|
|
|
|
0
|
my $stack = caller_data_brief($opt{stack} + 3); |
426
|
0
|
|
|
|
|
0
|
shift @$stack; |
427
|
0
|
|
|
|
|
0
|
shift @$stack; |
428
|
0
|
|
|
|
|
0
|
shift @$stack; |
429
|
0
|
|
|
|
|
0
|
@$stack = reverse @$stack; |
430
|
0
|
|
|
|
|
0
|
my $i = scalar @$stack; |
431
|
|
|
|
|
|
|
#my $i = $opt{stack}; |
432
|
0
|
|
|
|
|
0
|
print STDERR $newline; |
433
|
0
|
|
|
|
|
0
|
foreach my $data (@$stack) { |
434
|
|
|
|
|
|
|
print STDERR '((stack ' . sprintf("%2s",$i--) . ')) ' . sprintf("%7s",'[' . $data->{line} . ']') . ' ' . |
435
|
0
|
|
|
|
|
0
|
GREEN . $data->{subroutine} . CLEAR . $newline; |
436
|
|
|
|
|
|
|
} |
437
|
0
|
|
|
|
|
0
|
print STDERR '((stack 0)) ' . sprintf("%7s",'[' . $opt{line} . ']') . ' ' . |
438
|
|
|
|
|
|
|
GREEN . $class . '::' . $name . $newline . CLEAR; |
439
|
0
|
|
|
|
|
0
|
$class = "$self"; |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
else { |
442
|
0
|
0
|
|
|
|
0
|
print STDERR $newline if ($new_nest); |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
print STDERR '[' . $opt{line} . '] ' . CLEAR . $opt{color} . $class . CLEAR . '->' . |
446
|
0
|
|
|
|
|
0
|
$opt{color} . BOLD . $name . CLEAR . $in; |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
0
|
my $spaces = ' ' x (2 + length($opt{line})); |
449
|
|
|
|
|
|
|
my $in_func = sub { |
450
|
|
|
|
|
|
|
print STDERR $newline . ON_WHITE.BOLD . BLUE . "$spaces Supplied arguments dump: " . |
451
|
|
|
|
|
|
|
$opt{dump_func}->($opt{verbose_in},\@args) . CLEAR . $newline . ": " |
452
|
0
|
0
|
0
|
|
|
0
|
if($has_refs && $opt{verbose_in}); |
453
|
0
|
|
|
|
|
0
|
}; |
454
|
|
|
|
|
|
|
|
455
|
0
|
|
|
|
|
0
|
my $res; |
456
|
|
|
|
|
|
|
my @res; |
457
|
0
|
|
|
|
|
0
|
my @res_copy = (); |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# before timestamp: |
460
|
0
|
|
|
|
|
0
|
my $t0 = [gettimeofday]; |
461
|
0
|
|
|
|
|
0
|
my $current_nest_elapse; |
462
|
|
|
|
|
|
|
{ |
463
|
0
|
|
|
|
|
0
|
local $debug_around_nest_elapse = 0; |
|
0
|
|
|
|
|
0
|
|
464
|
0
|
0
|
|
|
|
0
|
if(wantarray) { |
465
|
|
|
|
|
|
|
try { |
466
|
0
|
|
|
|
|
0
|
@res = $opt{around}->($orig,$self,@args); |
467
|
0
|
|
|
|
|
0
|
} catch { $in_func->(); die (shift);}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
468
|
0
|
|
|
|
|
0
|
push @res_copy, @res; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
else { |
471
|
|
|
|
|
|
|
try { |
472
|
0
|
|
|
|
|
0
|
$res = $opt{around}->($orig,$self,@args); |
473
|
0
|
|
|
|
|
0
|
} catch { $in_func->(); die (shift);}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
474
|
0
|
|
|
|
|
0
|
push @res_copy,$res; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
# How much of the elapsed time was in nested funcs below us: |
477
|
0
|
|
|
|
|
0
|
$current_nest_elapse = $debug_around_nest_elapse; |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# after timestamp, calculate elapsed (to the millisecond): |
481
|
0
|
|
|
|
|
0
|
my $elapsed_raw = tv_interval($t0); |
482
|
0
|
|
|
|
|
0
|
my $adj_elapsed = $elapsed_raw - $current_nest_elapse; |
483
|
0
|
|
|
|
|
0
|
$debug_around_nest_elapse += $elapsed_raw; #<-- send our elapsed time up the chain |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# -- Track stats in global %$RapidApp::Util::debug_around_stats: |
486
|
0
|
0
|
|
|
|
0
|
if($opt{track_stats}) { |
487
|
6
|
|
|
6
|
|
49
|
no warnings 'uninitialized'; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
10993
|
|
488
|
0
|
|
|
|
|
0
|
my $k = $class . '->' . $name; |
489
|
0
|
|
0
|
|
|
0
|
$debug_around_stats->{$k} = $debug_around_stats->{$k} || {}; |
490
|
0
|
|
|
|
|
0
|
my $stats = $debug_around_stats->{$k}; |
491
|
|
|
|
|
|
|
%$stats = ( |
492
|
|
|
|
|
|
|
class => $class, |
493
|
|
|
|
|
|
|
sub => $name, |
494
|
|
|
|
|
|
|
line => $opt{line}, |
495
|
|
|
|
|
|
|
calls => $stats->{calls} + 1, |
496
|
|
|
|
|
|
|
real_total => $stats->{real_total} + $elapsed_raw, |
497
|
|
|
|
|
|
|
total => $stats->{total} + $adj_elapsed, |
498
|
|
|
|
|
|
|
min => exists $stats->{min} ? $stats->{min} : $adj_elapsed, |
499
|
0
|
0
|
|
|
|
0
|
max => exists $stats->{max} ? $stats->{max} : $adj_elapsed, |
|
|
0
|
|
|
|
|
|
500
|
|
|
|
|
|
|
); |
501
|
0
|
|
|
|
|
0
|
$stats->{avg} = $stats->{total}/$stats->{calls}; |
502
|
0
|
0
|
|
|
|
0
|
$stats->{min} = $adj_elapsed if ($adj_elapsed < $stats->{min}); |
503
|
0
|
0
|
|
|
|
0
|
$stats->{max} = $adj_elapsed if ($adj_elapsed > $stats->{max}); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
# -- |
506
|
|
|
|
|
|
|
|
507
|
0
|
|
|
|
|
0
|
local $_ = $self; |
508
|
0
|
0
|
0
|
|
|
0
|
if(!$opt{arg_ignore}->(@args) && !$opt{return_ignore}->(@res_copy)) { |
509
|
|
|
|
|
|
|
|
510
|
0
|
|
|
|
|
0
|
$in_func->(); |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
#my $elapsed_short = '[' . sprintf("%.3f", $elapsed_raw ) . 's]'; |
513
|
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
0
|
my @a = map { sprintf('%.3f',$_) } ($elapsed_raw,$adj_elapsed); |
|
0
|
|
|
|
|
0
|
|
515
|
0
|
|
|
|
|
0
|
my $elapsed_long = '[' . join('|',@a) . ']'; |
516
|
|
|
|
|
|
|
|
517
|
0
|
|
|
|
|
0
|
my $result = $opt{ret_color} . $opt{dump_func}->($opt{verbose_out},@res_copy) . CLEAR; |
518
|
0
|
0
|
|
|
|
0
|
$result = "\n" . ON_WHITE.BOLD . "$spaces Returned: " . $result . "\n" if ($opt{verbose_out}); |
519
|
0
|
0
|
|
|
|
0
|
$result .= ' ' . ON_WHITE.RED . $elapsed_long . CLEAR if ($opt{time}); |
520
|
|
|
|
|
|
|
|
521
|
0
|
|
|
|
|
0
|
$result =~ s/\n/${newline}/gm; |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# Reset cursor position if nesting happened: |
524
|
0
|
0
|
|
|
|
0
|
print STDERR "\r$indent" unless ($RapidApp::Util::debug_around_last_nest_level == $nest_level); |
525
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
0
|
print STDERR $result . $newline; |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
else { |
530
|
|
|
|
|
|
|
# 'arg_ignore' and/or 'return_ignore' returned true, so we're not |
531
|
|
|
|
|
|
|
# supposed to print anything... but since we already have, in case |
532
|
|
|
|
|
|
|
# the function would have barfed, we'll print a \r to move the cursor |
533
|
|
|
|
|
|
|
# to the begining of the line so it will get overwritten, which is |
534
|
|
|
|
|
|
|
# almost as good as if we had not printed anything in the first place... |
535
|
|
|
|
|
|
|
# (note if the function printed something too we're screwed) |
536
|
0
|
|
|
|
|
0
|
print STDERR "\r"; |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
0
|
0
|
|
|
|
0
|
return wantarray ? @res : $res; |
540
|
0
|
|
|
|
|
0
|
}; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# Lets you create a sub and set debug_around on it at the same time |
544
|
|
|
|
|
|
|
sub debug_sub($&) { |
545
|
0
|
|
|
0
|
0
|
0
|
my ($pkg,$filename,$line) = caller; |
546
|
0
|
|
|
|
|
0
|
my ($name,$code) = @_; |
547
|
|
|
|
|
|
|
|
548
|
0
|
|
|
|
|
0
|
my $meta = Class::MOP::Class->initialize($pkg); |
549
|
0
|
|
|
|
|
0
|
$meta->add_method($name,$code); |
550
|
|
|
|
|
|
|
|
551
|
0
|
|
|
|
|
0
|
return debug_around $name, pkg => $pkg, filename => $filename, line => $line; |
552
|
|
|
|
|
|
|
} |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
sub debug_around_all { |
555
|
0
|
|
0
|
0
|
0
|
0
|
my $pkg = shift || caller; |
556
|
0
|
|
|
|
|
0
|
my $meta = Class::MOP::Class->initialize($pkg); |
557
|
0
|
|
|
|
|
0
|
debug_around($_, pkg => $pkg) for ($meta->get_method_list); |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
# Returns a stat in a hash with named keys |
561
|
|
|
|
|
|
|
sub xstat { |
562
|
0
|
|
|
0
|
0
|
0
|
my $file = shift; |
563
|
0
|
0
|
|
|
|
0
|
return undef unless (-e $file); |
564
|
0
|
|
|
|
|
0
|
my $h = {}; |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
($h->{dev},$h->{ino},$h->{mode},$h->{nlink},$h->{uid},$h->{gid},$h->{rdev}, |
567
|
|
|
|
|
|
|
$h->{size},$h->{atime},$h->{mtime},$h->{ctime},$h->{blksize},$h->{blocks}) |
568
|
0
|
|
|
|
|
0
|
= stat($file); |
569
|
|
|
|
|
|
|
|
570
|
0
|
|
|
|
|
0
|
return $h; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
##### From RapidApp::Sugar ##### |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
sub asjson { |
577
|
0
|
0
|
|
0
|
0
|
0
|
scalar(@_) == 1 or die "Expected single argument"; |
578
|
0
|
|
|
|
|
0
|
return RapidApp::JSON::MixedEncoder::encode_json($_[0]); |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# Bless a string as RawJavascript so that it doesn't get encoded as JSON data during asjson |
582
|
|
|
|
|
|
|
sub rawjs { |
583
|
0
|
0
|
0
|
0
|
0
|
0
|
scalar(@_) == 1 && ref $_[0] eq '' or die "Expected single string argument"; |
584
|
0
|
|
|
|
|
0
|
return RapidApp::JSON::RawJavascript->new(js=>$_[0]); |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
# Works like rawjs but accepts a list of arguments. Each argument should be a function defintion, |
588
|
|
|
|
|
|
|
# and will be stacked together, passing each function in the chain through the first argument |
589
|
|
|
|
|
|
|
sub jsfunc { |
590
|
2093
|
50
|
|
2093
|
0
|
3818
|
my $js = shift or die "jsfunc(): At least one argument is required"; |
591
|
|
|
|
|
|
|
|
592
|
2093
|
100
|
|
|
|
4266
|
return jsfunc(@$js) if (ref($js) eq 'ARRAY'); |
593
|
|
|
|
|
|
|
|
594
|
1509
|
50
|
66
|
|
|
3760
|
blessed $js and not $js->can('TO_JSON_RAW') and |
595
|
|
|
|
|
|
|
die "jsfunc: arguments must be JavaScript function definition strings or objects with TO_JSON_RAW methods"; |
596
|
|
|
|
|
|
|
|
597
|
1509
|
100
|
|
|
|
2523
|
$js = $js->TO_JSON_RAW if (blessed $js); |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# Remove undef arguments: |
600
|
1509
|
|
|
|
|
2023
|
@_ = grep { defined $_ } @_; |
|
918
|
|
|
|
|
2512
|
|
601
|
|
|
|
|
|
|
|
602
|
1509
|
100
|
|
|
|
4247
|
$js = 'function(){ ' . |
603
|
|
|
|
|
|
|
'var args = arguments; ' . |
604
|
|
|
|
|
|
|
'args[0] = (' . $js . ').apply(this,arguments); ' . |
605
|
|
|
|
|
|
|
'return (' . jsfunc(@_) . ').apply(this,args); ' . |
606
|
|
|
|
|
|
|
'}' if (scalar @_ > 0); |
607
|
|
|
|
|
|
|
|
608
|
1509
|
|
|
|
|
39806
|
return RapidApp::JSON::RawJavascript->new(js=>$js) |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# Encode a mix of javascript and data into appropriate objects that will get converted |
612
|
|
|
|
|
|
|
# to JSON properly during "asjson". |
613
|
|
|
|
|
|
|
# |
614
|
|
|
|
|
|
|
# Example: mixedjs "function() { var data=", { a => $foo, b => $bar }, "; Ext.msg.alert(data); }"; |
615
|
|
|
|
|
|
|
# See ScriptWithData for more details. |
616
|
|
|
|
|
|
|
# |
617
|
|
|
|
|
|
|
sub mixedjs { |
618
|
0
|
|
|
0
|
0
|
|
return RapidApp::JSON::ScriptWithData->new(@_); |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
# Take a string of text/plain and convert it to text/html. This handles "RawHtml" objects. |
622
|
|
|
|
|
|
|
sub ashtml { |
623
|
0
|
|
|
0
|
0
|
|
my $text= shift; |
624
|
0
|
0
|
0
|
|
|
|
return "$text" if ref($text) && ref($text)->isa('RapidApp::HTML::RawHtml'); |
625
|
0
|
0
|
|
|
|
|
return undef unless defined $text; |
626
|
0
|
|
|
|
|
|
return join('<br />', map { encode_entities($_) } split("\n", "$text")); |
|
0
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
# Bless a scalar to indicate the scalar is already html, and doesn't need converted. |
630
|
|
|
|
|
|
|
sub rawhtml { |
631
|
0
|
|
|
0
|
0
|
|
my $html= shift; |
632
|
|
|
|
|
|
|
# any other arguments we were given, we pass back in hopes that we're part of a function call that needed them. |
633
|
0
|
|
|
|
|
|
return RapidApp::HTML::RawHtml->new($html), @_; |
634
|
|
|
|
|
|
|
} |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=head2 usererr $message, key => $value, key => $value |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
Shorthand notation to create a UserError, to inform the user they did something wrong. |
639
|
|
|
|
|
|
|
First argument is a scalar of text (or a RawHtml scalar of html) |
640
|
|
|
|
|
|
|
Second through N arguments are hash keys to apply to the UserError constructor. |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
Examples: |
643
|
|
|
|
|
|
|
# To throw a message to the user with no data and no error report: |
644
|
|
|
|
|
|
|
die usererr "Hey you moron, don't do that"; |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
# To specify that your message is html already: |
647
|
|
|
|
|
|
|
die usererr rawhtml "<h2>Hell Yeah</h2>"; |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
=cut |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
my %keyAliases = ( |
652
|
|
|
|
|
|
|
msg => 'message', |
653
|
|
|
|
|
|
|
umsg => 'userMessage', |
654
|
|
|
|
|
|
|
title => 'userMessageTitle', |
655
|
|
|
|
|
|
|
); |
656
|
|
|
|
|
|
|
sub usererr { |
657
|
0
|
|
|
0
|
1
|
|
my %args= (); |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# First arg is always the message. We stringify it, so it doesn't matter if it was an object. |
660
|
0
|
|
|
|
|
|
my $msg= shift; |
661
|
0
|
0
|
|
|
|
|
defined $msg or die "userexception requires at least a first message argument"; |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
# If the passed arg is already a UserError object, return it as-is: |
664
|
0
|
0
|
0
|
|
|
|
return $msg if ref($msg) && ref($msg)->isa('RapidApp::Responder::UserError'); |
665
|
|
|
|
|
|
|
|
666
|
0
|
0
|
0
|
|
|
|
$args{userMessage}= ref($msg) && ref($msg)->isa('RapidApp::HTML::RawHtml')? $msg : "$msg"; |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# pull in any other args |
669
|
0
|
|
|
|
|
|
while (scalar(@_) > 1) { |
670
|
0
|
|
|
|
|
|
my ($key, $val)= (shift, shift); |
671
|
0
|
|
0
|
|
|
|
$key = $keyAliases{$key} || $key; |
672
|
0
|
0
|
|
|
|
|
RapidApp::Responder::UserError->can($key) |
673
|
|
|
|
|
|
|
or warn "Invalid attribute for UserError: $key"; |
674
|
0
|
|
|
|
|
|
$args{$key}= $val; |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
# userexception is allowed to have a payload at the end, but this would be meaningless for usererr, |
678
|
|
|
|
|
|
|
# since usererr is not saved. |
679
|
0
|
0
|
|
|
|
|
if (scalar(@_)) { |
680
|
0
|
|
|
|
|
|
my ($pkg, $file, $line)= caller; |
681
|
0
|
|
|
|
|
|
warn "Odd number of arguments to usererr at $file:$line"; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
0
|
|
|
|
|
|
return RapidApp::Responder::UserError->new(\%args); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=head2 userexception $message, key => $value, key => $value, \%data |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
Shorthand notation for creating a RapidApp::Error which also informs the user about why the error occured. |
690
|
|
|
|
|
|
|
First argument is the message displayed to the user (can be a RawHtml object). |
691
|
|
|
|
|
|
|
Last argument is a hash of data that should be saved for the error report. |
692
|
|
|
|
|
|
|
( the last argument is equivalent to a value for an implied hash key of "data" ) |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
Examples: |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
# Die with a custom user-facing message (in plain text), and a title made of html. |
697
|
|
|
|
|
|
|
die userexception "Description of what shouldn't have happened", title => rawhtml "<h1>ERROR</h1>"; |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
# Capture some data for the error report, as we show this message to the user. |
700
|
|
|
|
|
|
|
die userexception "Description of what shouldn't have happened", $some_debug_info; |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=cut |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
sub userexception { |
705
|
0
|
|
|
0
|
1
|
|
my %args= (); |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# First arg is always the message. We stringify it, so it doesn't matter if it was an object. |
708
|
0
|
|
|
|
|
|
my $msg= shift; |
709
|
0
|
0
|
|
|
|
|
defined $msg or die "userexception requires at least a first message argument"; |
710
|
0
|
0
|
0
|
|
|
|
$args{userMessage}= ref($msg) && ref($msg)->isa('RapidApp::HTML::RawHtml')? $msg : "$msg"; |
711
|
0
|
|
|
|
|
|
$args{message}= $args{userMessage}; |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# pull in any other args |
714
|
0
|
|
|
|
|
|
while (scalar(@_) > 1) { |
715
|
0
|
|
|
|
|
|
my ($key, $val)= (shift, shift); |
716
|
0
|
|
0
|
|
|
|
$key = $keyAliases{$key} || $key; |
717
|
0
|
0
|
|
|
|
|
RapidApp::Error->can($key) |
718
|
|
|
|
|
|
|
or warn "Invalid attribute for RapidApp::Error: $key"; |
719
|
0
|
|
|
|
|
|
$args{$key}= $val; |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
# userexception is allowed to have a payload as the last argument |
723
|
0
|
0
|
|
|
|
|
if (scalar(@_)) { |
724
|
0
|
|
|
|
|
|
$args{data}= shift; |
725
|
|
|
|
|
|
|
} |
726
|
|
|
|
|
|
|
|
727
|
0
|
|
|
|
|
|
return RapidApp::Error->new(\%args); |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
# Suger function sets up a Native Trait ArrayRef attribute with useful |
733
|
|
|
|
|
|
|
# default accessor methods |
734
|
|
|
|
|
|
|
#sub hasarray { |
735
|
|
|
|
|
|
|
# my $name = shift; |
736
|
|
|
|
|
|
|
# my %opt = @_; |
737
|
|
|
|
|
|
|
# |
738
|
|
|
|
|
|
|
# my %defaults = ( |
739
|
|
|
|
|
|
|
# is => 'ro', |
740
|
|
|
|
|
|
|
# isa => 'ArrayRef', |
741
|
|
|
|
|
|
|
# traits => [ 'Array' ], |
742
|
|
|
|
|
|
|
# default => sub {[]}, |
743
|
|
|
|
|
|
|
# handles => { |
744
|
|
|
|
|
|
|
# 'all_' . $name => 'uniq', |
745
|
|
|
|
|
|
|
# 'add_' . $name => 'push', |
746
|
|
|
|
|
|
|
# 'insert_' . $name => 'unshift', |
747
|
|
|
|
|
|
|
# 'has_no_' . $name => 'is_empty', |
748
|
|
|
|
|
|
|
# 'count_' . $name => 'count' |
749
|
|
|
|
|
|
|
# } |
750
|
|
|
|
|
|
|
# ); |
751
|
|
|
|
|
|
|
# |
752
|
|
|
|
|
|
|
# my $conf = merge(\%defaults,\%opt); |
753
|
|
|
|
|
|
|
# return caller->can('has')->($name,%$conf); |
754
|
|
|
|
|
|
|
#} |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
# Suger function sets up a Native Trait HashRef attribute with useful |
757
|
|
|
|
|
|
|
# default accessor methods |
758
|
|
|
|
|
|
|
#sub hashash { |
759
|
|
|
|
|
|
|
# my $name = shift; |
760
|
|
|
|
|
|
|
# my %opt = @_; |
761
|
|
|
|
|
|
|
# |
762
|
|
|
|
|
|
|
# my %defaults = ( |
763
|
|
|
|
|
|
|
# is => 'ro', |
764
|
|
|
|
|
|
|
# isa => 'HashRef', |
765
|
|
|
|
|
|
|
# traits => [ 'Hash' ], |
766
|
|
|
|
|
|
|
# default => sub {{}}, |
767
|
|
|
|
|
|
|
# handles => { |
768
|
|
|
|
|
|
|
# 'apply_' . $name => 'set', |
769
|
|
|
|
|
|
|
# 'get_' . $name => 'get', |
770
|
|
|
|
|
|
|
# 'has_' . $name => 'exists', |
771
|
|
|
|
|
|
|
# 'all_' . $name => 'values', |
772
|
|
|
|
|
|
|
# $name . '_names' => 'keys', |
773
|
|
|
|
|
|
|
# } |
774
|
|
|
|
|
|
|
# ); |
775
|
|
|
|
|
|
|
# |
776
|
|
|
|
|
|
|
# my $conf = merge(\%defaults,\%opt); |
777
|
|
|
|
|
|
|
# return caller->can('has')->($name,%$conf); |
778
|
|
|
|
|
|
|
#} |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub infostatus { |
782
|
0
|
|
|
0
|
0
|
|
my %opt = @_; |
783
|
0
|
0
|
|
|
|
|
%opt = ( msg => $_[0] ) if (@_ == 1); |
784
|
0
|
|
|
|
|
|
return RapidApp::Responder::InfoStatus->new(%opt); |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
# ----- |
789
|
|
|
|
|
|
|
# New sugar automates usage of CustomPrompt for the purposes of a simple |
790
|
|
|
|
|
|
|
# message with Ok/Cancel buttons. Returns the string name of the button |
791
|
|
|
|
|
|
|
# after the prompt round-trip. Example usage: |
792
|
|
|
|
|
|
|
# |
793
|
|
|
|
|
|
|
# if(throw_prompt_ok("really blah?") eq 'Ok') { |
794
|
|
|
|
|
|
|
# # do blah ... |
795
|
|
|
|
|
|
|
# } |
796
|
|
|
|
|
|
|
# |
797
|
|
|
|
|
|
|
sub throw_prompt_ok { |
798
|
0
|
|
|
0
|
0
|
|
my $msg; |
799
|
0
|
0
|
0
|
|
|
|
$msg = shift if (scalar(@_) % 2 && ! (ref $_[0])); # argument list is odd, and first arg not a ref |
800
|
|
|
|
|
|
|
|
801
|
0
|
0
|
0
|
|
|
|
my %opt = (ref($_[0]) && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; |
|
0
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
|
803
|
0
|
|
0
|
|
|
|
$msg ||= $opt{msg}; |
804
|
0
|
0
|
|
|
|
|
$msg or die 'throw_prompt_ok(): must supply a "msg" as either first arg or named in hash key'; |
805
|
|
|
|
|
|
|
|
806
|
0
|
0
|
|
|
|
|
my $c = RapidApp->active_request_context or die join(' ', |
807
|
|
|
|
|
|
|
'throw_prompt_ok(): this sugar function can only be called from', |
808
|
|
|
|
|
|
|
'within the context of an active request' |
809
|
|
|
|
|
|
|
); |
810
|
|
|
|
|
|
|
|
811
|
0
|
0
|
|
|
|
|
$c->is_ra_ajax_req or die die join(' ', |
812
|
|
|
|
|
|
|
'throw_prompt_ok(): this sugar function can only be called from', |
813
|
|
|
|
|
|
|
'within the context of a RapidApp-generated Ajax request' |
814
|
|
|
|
|
|
|
); |
815
|
|
|
|
|
|
|
|
816
|
0
|
|
|
|
|
|
my %cust_prompt = ( |
817
|
|
|
|
|
|
|
title => 'Confirm', |
818
|
|
|
|
|
|
|
items => { |
819
|
|
|
|
|
|
|
html => $msg |
820
|
|
|
|
|
|
|
}, |
821
|
|
|
|
|
|
|
formpanel_cnf => { |
822
|
|
|
|
|
|
|
defaults => {} |
823
|
|
|
|
|
|
|
}, |
824
|
|
|
|
|
|
|
validate => \1, |
825
|
|
|
|
|
|
|
noCancel => \1, |
826
|
|
|
|
|
|
|
buttons => [ 'Ok', 'Cancel' ], |
827
|
|
|
|
|
|
|
EnterButton => 'Ok', |
828
|
|
|
|
|
|
|
EscButton => 'Cancel', |
829
|
|
|
|
|
|
|
height => 175, |
830
|
|
|
|
|
|
|
width => 350, |
831
|
|
|
|
|
|
|
%opt |
832
|
|
|
|
|
|
|
); |
833
|
|
|
|
|
|
|
|
834
|
0
|
0
|
|
|
|
|
if (my $button = $c->req->header('X-RapidApp-CustomPrompt-Button')){ |
835
|
|
|
|
|
|
|
# $button should contain 'Ok' or 'Cancel' (or whatever values were set in 'buttons') |
836
|
0
|
|
|
|
|
|
return $button; |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
|
839
|
0
|
|
|
|
|
|
die RapidApp::Responder::CustomPrompt->new(\%cust_prompt); |
840
|
|
|
|
|
|
|
} |
841
|
|
|
|
|
|
|
# ----- |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
########################################################################################## |
847
|
|
|
|
|
|
|
########################################################################################## |
848
|
|
|
|
|
|
|
# |
849
|
|
|
|
|
|
|
# Automatically export all functions defined above: |
850
|
|
|
|
|
|
|
|
851
|
6
|
|
|
6
|
|
53
|
use Class::MOP::Class; |
|
6
|
|
|
|
|
24
|
|
|
6
|
|
|
|
|
1449
|
|
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
my @pkg_methods = grep { ! ($_ =~ /^_/) } ( # Do not export funcs that start with '_' |
854
|
|
|
|
|
|
|
Class::MOP::Class |
855
|
|
|
|
|
|
|
->initialize(__PACKAGE__) |
856
|
|
|
|
|
|
|
->get_method_list |
857
|
|
|
|
|
|
|
); |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
push @EXPORT_OK, @pkg_methods; |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# |
862
|
|
|
|
|
|
|
########################################################################################## |
863
|
|
|
|
|
|
|
########################################################################################## |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# The same as Catalyst::Utils::home but just a little bit more clever: |
866
|
|
|
|
|
|
|
sub find_app_home { |
867
|
0
|
0
|
0
|
0
|
0
|
|
$_[0] && $_[0] eq __PACKAGE__ and shift; |
868
|
|
|
|
|
|
|
|
869
|
0
|
|
|
|
|
|
require Catalyst::Utils; |
870
|
0
|
|
|
|
|
|
require Module::Locate; |
871
|
|
|
|
|
|
|
|
872
|
0
|
0
|
|
|
|
|
my $class = shift or die "find_app_home(): expected app class name argument"; |
873
|
|
|
|
|
|
|
|
874
|
0
|
|
|
|
|
|
my $path = Catalyst::Utils::home($class); |
875
|
|
|
|
|
|
|
|
876
|
0
|
0
|
|
|
|
|
unless($path) { |
877
|
|
|
|
|
|
|
# make an $INC{ $key } style string from the class name |
878
|
0
|
|
|
|
|
|
(my $file = "$class.pm") =~ s{::}{/}g; |
879
|
0
|
0
|
|
|
|
|
unless ($INC{$file}) { |
880
|
0
|
0
|
|
|
|
|
if(my $pm_path = Module::Locate::locate($class)) { |
881
|
0
|
|
|
|
|
|
local $INC{$file} = $pm_path; |
882
|
0
|
|
|
|
|
|
$path = Catalyst::Utils::home($class); |
883
|
|
|
|
|
|
|
} |
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
} |
886
|
|
|
|
|
|
|
|
887
|
0
|
|
|
|
|
|
return $path; |
888
|
|
|
|
|
|
|
} |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
1; |