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   3553 use strict;
  8         18  
  8         238  
23 8     8   4430 use Encode;
  8         82296  
  8         655  
24 8     8   3302 use XAO::Errors qw(XAO::Utils);
  8         26  
  8         57  
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         22  
  8         460  
44             $VERSION='2.7';
45              
46             ###############################################################################
47             # Export control
48             #
49 8     8   61 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  8         16  
  8         3659  
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 1319165 my $length=$_[0] || 8;
133              
134 100000 100       150565 if(!$generate_key_alpha) {
135             # 1 1 2 2 3
136             # 0----5----0----5----0----5----0-
137 1         4 $generate_key_alpha= 'ABCDEFGHIJKLMNOPQRSTUWXYZ';
138 1         7 $generate_key_alnum='2345689'.$generate_key_alpha;
139 1         4 $generate_key_alpha_len=length($generate_key_alpha);
140 1         6 $generate_key_alnum_len=length($generate_key_alnum);
141             }
142              
143 100000         156898 my $key=substr($generate_key_alpha,rand($generate_key_alpha_len),1);
144              
145 100000         176156 for(my $i=1; $i!=$length; $i++) {
146 700000         1242707 $key.=substr($generate_key_alnum,rand($generate_key_alnum_len),1);
147             }
148              
149 100000         188571 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 39 { my $key=uc($_[0]);
172 1         16 $key=~s/[\r\n\s]//sg;
173 1 50       6 return undef unless length($key) == 8;
174 1         18 $key=~s/0/O/g;
175 1         14 $key=~s/1/I/g;
176 1         4 $key=~s/7/I/g;
177 1         9 $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   63 use vars qw($debug_flag $logprint_handler);
  8         12  
  8         10056  
202              
203             ###############################################################################
204              
205             sub logprint ($) {
206 4 100   4 0 18 if($logprint_handler) {
207 3         5 &{$logprint_handler}($_[0]);
  3         23  
208             }
209             else {
210 1         53 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 74 100   74 1 2499 return unless $debug_flag;
240 2 50       12 my $str=join('',map { defined($_) ? $_ : '' } @_);
  4         30  
241 2         10 chomp $str;
242 2         19 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 25 my $str=join('',map { defined($_) ? $_ : '' } @_);
  2         30  
257 2         8 chomp $str;
258 2         25 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 170 my $newh=shift;
278 2         14 my $oldh=$logprint_handler;
279 2 100       16 if($newh) {
280 1 50       22 if(ref($newh) eq 'CODE') {
281 1         8 $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         6 $logprint_handler=undef;
289             }
290 2         12 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 15 my $old_flag=$debug_flag;
323 2         4 $debug_flag=$_[0];
324 2         6 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 167 my $text=t2ht($_[0]);
359 4         29 $text=~s/"/"/sg;
360 4         12 $text=~s/([\x00-\x1f<>])/'&#'.ord($1).';'/sge;
  0         0  
361 4         15 $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 705 my ($text,$encoding)=@_;
382              
383 8 100 50     95 my $bytes=Encode::is_utf8($text)
384             ? Encode::encode($encoding || 'utf8',$text)
385             : $text;
386              
387 8         78 $bytes=~s/([^[:ascii:]]|[\x00-\x20\&\?<>;"=%#\+])/"%".unpack("H2",$1)/sge;
  29         123  
388              
389 8         29 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 78 my $text=shift;
406 5         29 $text=~s/&/&/sg;
407 5         21 $text=~s/
408 5         28 $text=~s/>/>/sg;
409 5         22 return $text;
410             }
411              
412             ###############################################################################
413              
414             =item t2hj ($)
415              
416             Escapes text to look the same in JavaScript.
417              
418             ' ->> \u0027
419             " ->> \"
420             \ ->> \\
421             < ->> \u003c
422             > ->> \u003e
423              
424             Single quote is escaped into a hex code because that is acceptable in
425             both Javascript and JSON strings, whereas \' is not valid in JSON.
426              
427             Angle brackets are escaped because otherwise a value of inside
428             a field would close the outer script tag even though it's encountered in
429             the middle of a literal.
430              
431             =cut
432              
433             sub t2hj ($) {
434 7     7 1 216 my $text=shift;
435 7         22 $text=~s/\\/\\\\/sg;
436 7         25 $text=~s/'/\\u0027/sg;
437 7         17 $text=~s/"/\\"/sg;
438 7         35 $text=~s/([\x00-\x1f<>])/'\\u'.sprintf('%04x',ord($1))/esg;
  4         43  
439 7         28 return $text;
440             }
441              
442             ###############################################################################
443              
444             =back
445              
446             =head2 ARGUMENTS HANDLING
447              
448             Utility functions in this group are imported by default, their tag name is
449             `args'. For example if you need everything but them you can say:
450              
451             use XAO::Utils qw(:all !:args);
452              
453             Here is the list of functions available:
454              
455             =over
456              
457             =cut
458              
459             ###############################################################################
460              
461             =item get_args ($)
462              
463             Probably one of the most used functions throughout XAO
464             tools. Understands arguments in the variety of formats and always
465             returns a hash reference as the result.
466              
467             Undrestands arrays, array references and hash references.
468              
469             Should be used as follows:
470              
471             use XAO::Utils;
472              
473             sub my_method ($%) {
474             my $self=shift;
475             my $args=get_args(\@_);
476              
477             if($args->{mode} eq 'fubar') {
478             ...
479             }
480              
481             Now my_method could be called in either way:
482              
483             $self->my_method(mode => 'fubar');
484              
485             $self->my_method( { mode => 'fubar' } );
486              
487             Or even:
488              
489             $self->my_method( { mode => 'fubar' }, { submode => 'barfoo' });
490              
491             sub other_method ($%) {
492             my $self=shift;
493             my $args=get_args(\@_);
494              
495             if(some condition) {
496              
497             return $self->my_method($args);
498             }
499             ...
500              
501             sub debug_my_method ($%) {
502             my $self=shift;
503             dprint "will call my_method with our arguments";
504             $self->my_method(@_);
505             }
506              
507             Note, that in the above examples you could also use "get_args(@_)"
508             instead of "get_args(\@_)". That's fine and that will work, but
509             slower.
510              
511             =cut
512              
513             sub get_args (@) {
514 4295 100   4295 1 11099 my $arr=ref($_[0]) eq 'ARRAY' ? $_[0] : \@_;
515              
516 4295 50 33     14327 if(!@$arr) {
    100          
    100          
    50          
517 0         0 return { };
518             }
519             elsif(@$arr == 1) {
520 1787         2369 my $args=$arr->[0];
521 1787 50       3668 ref($args) eq 'HASH' ||
522             throw XAO::E::Utils "get_args - single argument not a hash ref";
523 1787         3085 return $args;
524             }
525             elsif(ref($arr->[0]) eq 'HASH') {
526 1         4 return merge_refs(@$arr);
527             }
528             elsif(!ref($arr->[0]) && (scalar(@$arr)%2)==0) {
529 2507         6245 my %a=@$arr;
530 2507         5798 return \%a;
531             }
532             else {
533 0         0 throw XAO::E::Utils "get_args - unparsable arguments";
534             }
535             }
536              
537             ###############################################################################
538              
539             =item merge_refs (@)
540              
541             Combines together multiple hash references into one without altering
542             original hashes. Can be used in situations when you want to pass along
543             slightly modified hash reference like that:
544              
545             sub some_wrapper (%) {
546             my $args=get_args(\@_);
547             real_method(merge_args($args,{ objname => 'Fubar' }));
548             }
549              
550             Any number of hash references can be passed, first has lowest priority.
551              
552             =cut
553              
554             sub merge_refs (@) {
555 67     67 1 158 my %hash;
556 67         167 foreach my $ref (@_) {
557 105 100       291 next unless defined $ref;
558 103         527 @hash{keys %$ref}=values %$ref;
559             }
560 67         244 \%hash;
561             }
562              
563             ###############################################################################
564              
565             =back
566              
567             =head2 MATH
568              
569             Utility functions in this group can be imported by using 'math' tag:
570              
571             use XAO::Utils qw(:math);
572              
573             Here is the list of functions available:
574              
575             =over
576              
577             =cut
578              
579             ###############################################################################
580              
581             =item fround ($$)
582              
583             Rounds a floating point number according to the given
584             precision.
585              
586             Precision is given as X in 1/X, for instance to round to two digits
587             after decimal point use precision 100.
588              
589             Examples:
590              
591             fround(0.25,10) => 0.3
592             fround(0.01234,1000) => 0.012
593              
594             =cut
595              
596             sub fround ($$) {
597 13     13 1 410 my ($num,$prec)=@_;
598              
599 13 50       45 $prec>0 || throw XAO::E::Utils "fround - no precision given";
600 13         33 $prec*=1.0;
601              
602             # Adding a very small amount is a dirty hack, but without it
603             # it is hard to deal with fround(7.42/0.8, 100) being 9.27 instead
604             # of 9.28.
605             #
606 13         24 my $d=1/($prec * 100_000);
607 13 100       28 if($num<0) {
608 4         8 $num-=$d;
609 4         22 return -(int((-$num+1/$prec/2)*$prec))/$prec;
610             }
611             else {
612 9         18 $num+=$d;
613 9         26 return (int(($num+1/$prec/2)*$prec))/$prec;
614             }
615             }
616              
617             ###############################################################################
618             1;
619             __END__