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