File Coverage

blib/lib/RapidApp/Util.pm
Criterion Covered Total %
statement 120 473 25.3
branch 19 234 8.1
condition 9 120 7.5
subroutine 34 63 53.9
pod 2 28 7.1
total 184 918 20.0


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