File Coverage

blib/lib/XAO/Utils.pm
Criterion Covered Total %
statement 102 107 95.3
branch 28 36 77.7
condition 3 7 42.8
subroutine 20 21 95.2
pod 14 15 93.3
total 167 186 89.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::Utils - Utility functions widely used by XAO suite
4              
5             =head1 SYNOPSIS
6              
7             use XAO::Utils (:all); # export everything
8              
9             or
10              
11             use XAO::Utils (:none); # do not export anything
12              
13             =head1 DESCRIPTION
14              
15             This is not an object, but a collection of useful utility
16             functions.
17              
18             =cut
19              
20             ###############################################################################
21             package XAO::Utils;
22 8     8   3335 use strict;
  8         19  
  8         229  
23 8     8   4509 use Encode;
  8         83502  
  8         626  
24 8     8   3336 use XAO::Errors qw(XAO::Utils);
  8         26  
  8         58  
25              
26             ##
27             # Prototypes
28             #
29             sub generate_key (;$);
30             sub repair_key ($);
31             sub set_debug ($);
32             sub get_debug ();
33             sub dprint (@);
34             sub eprint (@);
35             sub t2ht ($);
36             sub t2hf ($);
37             sub t2hq ($;$);
38             sub t2hj ($);
39             sub get_args (@);
40             sub merge_refs (@);
41             sub fround ($$);
42              
43 8     8   67 use vars qw($VERSION);
  8         16  
  8         468  
44             $VERSION='2.7';
45              
46             ###############################################################################
47             # Export control
48             #
49 8     8   59 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  8         14  
  8         3643  
50             require Exporter;
51             @ISA=qw(Exporter);
52             %EXPORT_TAGS=(
53             all => \@EXPORT_OK,
54             args => [qw(get_args merge_refs)],
55             debug => [qw(dprint eprint)],
56             html => [qw(t2ht t2hq t2hf t2hj)],
57             keys => [qw(generate_key repair_key)],
58             math => [qw(fround)],
59             none => [],
60             );
61             @EXPORT=(
62             @{$EXPORT_TAGS{args}},
63             @{$EXPORT_TAGS{debug}},
64             );
65             @EXPORT_OK=(
66             @{$EXPORT_TAGS{args}},
67             @{$EXPORT_TAGS{debug}},
68             @{$EXPORT_TAGS{html}},
69             @{$EXPORT_TAGS{keys}},
70             @{$EXPORT_TAGS{math}},
71             );
72              
73             ###############################################################################
74              
75             =head2 KEYS HANDLING
76              
77             Utility functions in this group can be imported by using 'keys' tag:
78              
79             use XAO::Utils qw(:keys);
80              
81             Here is the list of functions available:
82              
83             =over
84              
85             =cut
86              
87             ###############################################################################
88              
89             =item generate_key (;$)
90              
91             Generating new 8-characters random ID. Not guaranteed to be unique,
92             must be checked against existing database.
93              
94             Generated ID is relativelly suitable for humans - it does not contain
95             some letters and digits that could be easily misunderstood in writing:
96              
97             =over
98              
99             =item 0 (zero)
100              
101             Looks the same as letter O.
102              
103             =item 1 (one)
104              
105             Is almost undistinguishable from capital I
106              
107             =item 7
108              
109             Written by american is often taken as 1 by europeans and vice versa.
110              
111             =item V
112              
113             Is similar to U.
114              
115             =back
116              
117             Examples of generated IDs are E5TUVX82, ZK845LP6 and so on.
118              
119             The generated ID will never start with a digit!
120              
121             The default generated key length is 8. This can be changed by supplying
122             an optional argument -- generate_key(20) for example.
123              
124             =cut
125              
126             my $generate_key_alpha;
127             my $generate_key_alnum;
128             my $generate_key_alpha_len;
129             my $generate_key_alnum_len;
130              
131             sub generate_key (;$) {
132 100000   50 100000 1 1328538 my $length=$_[0] || 8;
133              
134 100000 100       154921 if(!$generate_key_alpha) {
135             # 1 1 2 2 3
136             # 0----5----0----5----0----5----0-
137 1         9 $generate_key_alpha= 'ABCDEFGHIJKLMNOPQRSTUWXYZ';
138 1         11 $generate_key_alnum='2345689'.$generate_key_alpha;
139 1         9 $generate_key_alpha_len=length($generate_key_alpha);
140 1         6 $generate_key_alnum_len=length($generate_key_alnum);
141             }
142              
143 100000         156756 my $key=substr($generate_key_alpha,rand($generate_key_alpha_len),1);
144              
145 100000         176317 for(my $i=1; $i!=$length; $i++) {
146 700000         1205990 $key.=substr($generate_key_alnum,rand($generate_key_alnum_len),1);
147             }
148              
149 100000         185390 return $key;
150             }
151              
152             ###############################################################################
153              
154             =item repair_key ($)
155              
156             Repairing human-entered ID. Similar letters and digits are substituted
157             to allowed ones.
158              
159             Example:
160              
161             my $ans=;
162             my $id=repair_key($ans);
163             die "Wrong ID" unless $id;
164             print "id=$id\n";
165              
166             If you enter "10qwexcv" to that script it will print "IOQWEXCU".
167              
168             =cut
169              
170             sub repair_key ($)
171 1     1 1 16 { my $key=uc($_[0]);
172 1         14 $key=~s/[\r\n\s]//sg;
173 1 50       17 return undef unless length($key) == 8;
174 1         11 $key=~s/0/O/g;
175 1         11 $key=~s/1/I/g;
176 1         5 $key=~s/7/I/g;
177 1         7 $key=~s/V/U/g;
178 1         5 $key;
179             }
180              
181             ###############################################################################
182              
183             =back
184              
185             =head2 DEBUGGING
186              
187             Utility functions in this group are imported by default, their tag name is
188             `debug'. In the rare event when you need everything but debug functions
189             you can say:
190              
191             use XAO::Utils qw(:all !:debug);
192              
193             Here is the list of functions available:
194              
195             =over
196              
197             =cut
198              
199             ###############################################################################
200              
201 8     8   67 use vars qw($debug_flag $logprint_handler);
  8         16  
  8         9957  
202              
203             ###############################################################################
204              
205             sub logprint ($) {
206 4 100   4 0 13 if($logprint_handler) {
207 3         5 &{$logprint_handler}($_[0]);
  3         7  
208             }
209             else {
210 1         48 print STDERR $_[0]."\n";
211             }
212             }
213              
214             ###############################################################################
215              
216             =item dprint (@)
217              
218             Prints all arguments just like normal "print" does but 1) it prints
219             them to STDERR or uses the handler provided by set_logprint_handler()
220             and 2) only if you called set_debug(1) somewhere above. Useful for
221             printing various debug messages and then looking at them in S<"tail -f
222             apache/logs/error_log">.
223              
224             Once you debugged your program you just turn off set_debug() somewhere at
225             the top and all output goes away.
226              
227             Example:
228              
229             @arr=parse_my_stuff();
230             dprint "Got Array: ",join(",",@arr);
231              
232             B Debugging status is global. In case of mod_perl environment
233             with multiple sites under the same Apache server you enable or disable
234             debugging for all sites at once.
235              
236             =cut
237              
238             sub dprint (@) {
239 38 100   38 1 1373 return unless $debug_flag;
240 2 50       15 my $str=join('',map { defined($_) ? $_ : '' } @_);
  4         28  
241 2         9 chomp $str;
242 2         18 logprint($str);
243             }
244              
245             ###############################################################################
246              
247             =item eprint (@)
248              
249             Prints all arguments to STDERR or using the handler provided by
250             set_logprint_handler() like dprint() does but unconditionally. Great for
251             reporting minor problems to the server log.
252              
253             =cut
254              
255             sub eprint (@) {
256 2 50   2 1 13 my $str=join('',map { defined($_) ? $_ : '' } @_);
  2         23  
257 2         6 chomp $str;
258 2         26 logprint('*ERROR: '.$str);
259             }
260              
261             ###############################################################################
262              
263             =item set_logprint_handler ($)
264              
265             Installs a handler to be used by eprint() and dprint(). Useful when
266             STDERR is not available or should not be used.
267              
268             Example:
269              
270             my $s=Apache->request->server;
271             XAO::Utils::set_logprint_handler(sub { $s->log_error($_[0] });
272             dprint "Using Apache error logging";
273              
274             =cut
275              
276             sub set_logprint_handler ($) {
277 2     2 1 168 my $newh=shift;
278 2         9 my $oldh=$logprint_handler;
279 2 100       17 if($newh) {
280 1 50       12 if(ref($newh) eq 'CODE') {
281 1         6 $logprint_handler=$newh;
282             }
283             else {
284 0         0 eprint "set_logprint_handler - bad handler '$newh', expected code reference";
285             }
286             }
287             else {
288 1         1 $logprint_handler=undef;
289             }
290 2         15 return $oldh;
291             }
292              
293             ###############################################################################
294              
295             =item get_debug ($)
296              
297             Returns boolean value of the current state of the debug flag.
298              
299             =cut
300              
301             sub get_debug () {
302 0     0 1 0 return $debug_flag;
303             }
304              
305             ###############################################################################
306              
307             =item set_debug ($)
308              
309             Turns debug flag on or off. The flag is global for all packages that
310             use XAO::Utils!
311              
312             Example:
313              
314             use XAO::Utils;
315              
316             XAO::Utils::set_debug(1);
317             dprint "dprint will now work!";
318              
319             =cut
320              
321             sub set_debug ($) {
322 2     2 1 13 my $old_flag=$debug_flag;
323 2         8 $debug_flag=$_[0];
324 2         9 return $old_flag;
325             }
326              
327             ###############################################################################
328              
329             =back
330              
331             =head2 HTML ENCODING
332              
333             Utility functions in this group can be imported by using 'html' tag:
334              
335             use XAO::Utils qw(:html);
336              
337             Here is the list of functions available:
338              
339             =over
340              
341             =cut
342              
343             ###############################################################################
344              
345             =item t2hf ($)
346              
347             Escapes text to be be included in HTML tags arguments. Can be used for
348             XAO::Web object arguments as well.
349              
350             " ->> "
351              
352             All symbols from 0x0 to 0x1f are substituted with their codes in &#NNN;
353             format.
354              
355             =cut
356              
357             sub t2hf ($) {
358 4     4 1 145 my $text=t2ht($_[0]);
359 4         26 $text=~s/"/"/sg;
360 4         16 $text=~s/([\x00-\x1f<>])/'&#'.ord($1).';'/sge;
  0         0  
361 4         18 $text;
362             }
363              
364             ###############################################################################
365              
366             =item t2hq ($;$)
367              
368             Escapes text to be be included into URL parameters.
369              
370             All symbols from 0x0 to 0x1f and from 0x80 to 0xff as well as the
371             symbols from [&?<>"=%#+] are substituted to %XX hexadecimal codes
372             interpreted by all standard CGI tools. The same conversion may be used
373             for URLs themselves.
374              
375             Unicode is encoded into UTF-8 (unless a different encoding is specified
376             in the second argument).
377              
378             =cut
379              
380             sub t2hq ($;$) {
381 8     8 1 1213 my ($text,$encoding)=@_;
382              
383 8 100 50     133 my $bytes=Encode::is_utf8($text)
384             ? Encode::encode($encoding || 'utf8',$text)
385             : $text;
386              
387 8         160 $bytes=~s/([^[:ascii:]]|[\x00-\x20\&\?<>;"=%#\+])/"%".unpack("H2",$1)/sge;
  29         168  
388              
389 8         46 return $bytes;
390             }
391              
392             ###############################################################################
393              
394             =item t2ht ($)
395              
396             Escapes text to look the same in HTML.
397              
398             & ->> &
399             > ->> >
400             < ->> <
401              
402             =cut
403              
404             sub t2ht ($) {
405 5     5 1 70 my $text=shift;
406 5         41 $text=~s/&/&/sg;
407 5         20 $text=~s/
408 5         20 $text=~s/>/>/sg;
409 5         17 return $text;
410             }
411              
412             ###############################################################################
413              
414             =item t2hj ($)
415              
416             Escapes text to look the same in JavaScript.
417              
418             ' ->> \'
419             " ->> \"
420             \ ->> \\
421              
422             =cut
423              
424             sub t2hj ($) {
425 6     6 1 337 my $text=shift;
426 6         32 $text=~s/\\/\\\\/sg;
427 6         35 $text=~s/'/\\'/sg;
428 6         33 $text=~s/"/\\"/sg;
429 6         63 $text=~s/([\x00-\x1f])/'\\'.sprintf('%03o',ord($1))/esg;
  2         54  
430 6         51 return $text;
431             }
432              
433             ###############################################################################
434              
435             =back
436              
437             =head2 ARGUMENTS HANDLING
438              
439             Utility functions in this group are imported by default, their tag name is
440             `args'. For example if you need everything but them you can say:
441              
442             use XAO::Utils qw(:all !:args);
443              
444             Here is the list of functions available:
445              
446             =over
447              
448             =cut
449              
450             ###############################################################################
451              
452             =item get_args ($)
453              
454             Probably one of the most used functions throughout XAO
455             tools. Understands arguments in the variety of formats and always
456             returns a hash reference as the result.
457              
458             Undrestands arrays, array references and hash references.
459              
460             Should be used as follows:
461              
462             use XAO::Utils;
463              
464             sub my_method ($%) {
465             my $self=shift;
466             my $args=get_args(\@_);
467              
468             if($args->{mode} eq 'fubar') {
469             ...
470             }
471              
472             Now my_method could be called in either way:
473              
474             $self->my_method(mode => 'fubar');
475              
476             $self->my_method( { mode => 'fubar' } );
477              
478             Or even:
479              
480             $self->my_method( { mode => 'fubar' }, { submode => 'barfoo' });
481              
482             sub other_method ($%) {
483             my $self=shift;
484             my $args=get_args(\@_);
485              
486             if(some condition) {
487              
488             return $self->my_method($args);
489             }
490             ...
491              
492             sub debug_my_method ($%) {
493             my $self=shift;
494             dprint "will call my_method with our arguments";
495             $self->my_method(@_);
496             }
497              
498             Note, that in the above examples you could also use "get_args(@_)"
499             instead of "get_args(\@_)". That's fine and that will work, but
500             slower.
501              
502             =cut
503              
504             sub get_args (@) {
505 2518 100   2518 1 7617 my $arr=ref($_[0]) eq 'ARRAY' ? $_[0] : \@_;
506              
507 2518 50 33     9269 if(!@$arr) {
    100          
    100          
    50          
508 0         0 return { };
509             }
510             elsif(@$arr == 1) {
511 899         1379 my $args=$arr->[0];
512 899 50       1758 ref($args) eq 'HASH' ||
513             throw XAO::E::Utils "get_args - single argument not a hash ref";
514 899         1808 return $args;
515             }
516             elsif(ref($arr->[0]) eq 'HASH') {
517 1         7 return merge_refs(@$arr);
518             }
519             elsif(!ref($arr->[0]) && (scalar(@$arr)%2)==0) {
520 1618         4916 my %a=@$arr;
521 1618         4051 return \%a;
522             }
523             else {
524 0         0 throw XAO::E::Utils "get_args - unparsable arguments";
525             }
526             }
527              
528             ###############################################################################
529              
530             =item merge_refs (@)
531              
532             Combines together multiple hash references into one without altering
533             original hashes. Can be used in situations when you want to pass along
534             slightly modified hash reference like that:
535              
536             sub some_wrapper (%) {
537             my $args=get_args(\@_);
538             real_method(merge_args($args,{ objname => 'Fubar' }));
539             }
540              
541             Any number of hash references can be passed, first has lowest priority.
542              
543             =cut
544              
545             sub merge_refs (@) {
546 55     55 1 152 my %hash;
547 55         143 foreach my $ref (@_) {
548 81 100       190 next unless defined $ref;
549 80         377 @hash{keys %$ref}=values %$ref;
550             }
551 55         217 \%hash;
552             }
553              
554             ###############################################################################
555              
556             =back
557              
558             =head2 MATH
559              
560             Utility functions in this group can be imported by using 'math' tag:
561              
562             use XAO::Utils qw(:math);
563              
564             Here is the list of functions available:
565              
566             =over
567              
568             =cut
569              
570             ###############################################################################
571              
572             =item fround ($$)
573              
574             Rounds a floating point number according to the given
575             precision.
576              
577             Precision is given as X in 1/X, for instance to round to two digits
578             after decimal point use precision 100.
579              
580             Examples:
581              
582             fround(0.25,10) => 0.3
583             fround(0.01234,1000) => 0.012
584              
585             =cut
586              
587             sub fround ($$) {
588 13     13 1 375 my ($num,$prec)=@_;
589              
590 13 50       31 $prec>0 || throw XAO::E::Utils "fround - no precision given";
591 13         28 $prec*=1.0;
592              
593             # Adding a very small amount is a dirty hack, but without it
594             # it is hard to deal with fround(7.42/0.8, 100) being 9.27 instead
595             # of 9.28.
596             #
597 13         25 my $d=1/($prec * 100_000);
598 13 100       37 if($num<0) {
599 4         7 $num-=$d;
600 4         12 return -(int((-$num+1/$prec/2)*$prec))/$prec;
601             }
602             else {
603 9         18 $num+=$d;
604 9         40 return (int(($num+1/$prec/2)*$prec))/$prec;
605             }
606             }
607              
608             ###############################################################################
609             1;
610             __END__