File Coverage

blib/lib/RapidApp/Util.pm
Criterion Covered Total %
statement 117 424 27.5
branch 19 220 8.6
condition 9 114 7.8
subroutine 33 62 53.2
pod 2 28 7.1
total 180 848 21.2


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;