File Coverage

blib/lib/Hub/Perl/Language.pm
Criterion Covered Total %
statement 115 401 28.6
branch 61 330 18.4
condition 12 84 14.2
subroutine 13 40 32.5
pod 29 29 100.0
total 230 884 26.0


line stmt bran cond sub pod time code
1             package Hub::Perl::Language;
2 1     1   4 use strict;
  1         2  
  1         27  
3 1     1   70972 use Compress::Zlib;
  1         85422  
  1         341  
4 1     1   11 use Hub qw/:lib/;
  1         2  
  1         8  
5             our $VERSION = '4.00043';
6             our @EXPORT = qw//;
7             our @EXPORT_OK = qw/
8             sizeof
9             check
10             expect
11             fear
12             abort
13             opts
14             objopts
15             cmdopts
16             hashopts
17             bestof
18             subst
19             getuid
20             getgid
21             max
22             min
23             flip
24             rmval
25             cpref
26             random_id
27             checksum
28             merge
29             flatten
30             replace
31             digout
32             diff
33             touch
34             intdiv
35             dice
36             indexmatch
37             /;
38              
39             # Sorting
40             our ($a,$b) = ();
41              
42             # Regular expression used for Hub::check comparisons
43 1     1   7 use constant EXPR_NUMERIC => '\A[+-]?[\d\.Ee_]+\Z';
  1         3  
  1         941  
44              
45             # Not all interpreters have getpwnam compiled in
46             eval ("getpwnam('')");
47             our $HAS_GETPWNAM = $@ ? 0 : 1;
48              
49             # Not all interpreters have getgrnam compiled in
50             eval ("getgrnam('')");
51             our $HAS_GETGRNAM = $@ ? 0 : 1;
52              
53             # ------------------------------------------------------------------------------
54             # sizeof - Integer size of hashes, arrays, and scalars
55             #
56             # sizeof \%hash
57             # sizeof \@array
58             # sizeof \$scalar_ref
59             # sizeof $scalar
60             # sizeof \%more, \@than, $one
61             #
62             # Sizes are computed as follows:
63             #
64             # HASH - Number of keys in the hash
65             # ARRAY - Number of elements
66             # SCALAR - Length as returned by C
67             #
68             # The total size of all arguments is returned.
69             # ------------------------------------------------------------------------------
70             #|test(match,3) sizeof( { a=>1, b=>2, c=>3 } ); # Hash
71             #|test(match,3) sizeof( [ 'a1', 'b2', 'c3' ] ); # Array
72             #|test(match,3) sizeof( "abc" ); # Scalar
73             #|test(match,3) sizeof( \"abc" ); # Scalar (ref)
74             #|test(match,0) sizeof( undef ); # Nothing
75             #|test(match,3) sizeof( "a", "b", "c" ); # Multiple values
76             # ------------------------------------------------------------------------------
77              
78             sub sizeof {
79 0     0 1 0 my $result = 0;
80 0         0 foreach my $unk ( @_ ) {
81 0 0       0 $result += !defined $unk
    0          
    0          
    0          
    0          
82             ? 0
83             : !ref($unk)
84             ? length($unk)
85             : isa($unk, 'HASH')
86             ? Hub::sizeof([keys %$unk])
87             : isa($unk, 'ARRAY')
88             ? $#$unk + 1
89             : ref($unk) =~ /^(SCALAR|REF)$/
90             ? Hub::sizeof($$unk)
91             : croak("Cannot compute size of: $unk");
92             }
93 0         0 return $result;
94             }#sizeof
95              
96             # ------------------------------------------------------------------------------
97             # check - True if all items in list pass the test.
98             #
99             # check [OPTIONS], [TEST], LIST
100             #
101             # OPTIONS:
102             #
103             # -opr (or|and|xor) Operator (default: 'and')
104             #
105             # TEST:
106             #
107             # -test (def|num|str|match|blessed|eval) Test type (default: 'def')
108             # -isa EXPR
109             # -ref EXPR
110             #
111             # OPERATORS:
112             #
113             # and True when all items pass the test.
114             # or True when any single item passes the test.
115             # xor Alternation pattern. True unless two consecutive values
116             # both pass or fail the test.
117             #
118             # BASIC TEST:
119             #
120             # def Items are defined
121             # num Items are numeric
122             # str Items are *not* numeric
123             #
124             # OTHER TESTS:
125             #
126             # match=EXPR Items match EXPR
127             # eval Items are eval'd and truth is based on $@. Note that the
128             # eval *actually* happens, so don't do anything that will
129             # break your code. The intention of this check is for:
130             #
131             #|test(!abort) my $compression = check( '-test=eval', 'use IO::Zlib' ) ? 1 : 0;
132             #
133             # STRUCTURE TESTS:
134             #
135             # blessed Items are blessed
136             # ref=EXPR Item's ref matches EXPR (does *not* include @ISA)
137             # isa=EXPR Item's ref or @ISA match EXPR. Much like UNIVERSAL::isa
138             # except allows regular expressions.
139             #
140             # ------------------------------------------------------------------------------
141             #|test(false) check( undef, undef, undef ); # none are defined
142             #|test(false) check( 1, undef ); # only one is defined
143             #|test(true) check( 1, 1 ); # both are defined
144             #|test(true) check( 1, undef, -opr => 'or' ); # one is defined
145             #|
146             #|test(false) check( -opr => 'xor', 1, 1 );
147             #|test(false) check( -opr => 'xor', undef, undef );
148             #|
149             #|test(true) check( -opr => 'xor', undef, 1 );
150             #|test(true) check( -opr => 'xor', 1, undef );
151             #|
152             #|test(true) check( -opr => 'xor', 1, undef, 1, undef );
153             #|test(false) check( -opr => 'xor', 1, undef, 1, 1, undef );
154             #|test(true) check( -opr => 'xor', undef, 1, undef, 1 );
155             # ------------------------------------------------------------------------------
156              
157             sub check {
158              
159 37     37 1 176 my $opts = {
160            
161             'test' => 'def',
162             'opr' => 'and',
163             'match' => '',
164             'isa' => '',
165             'ref' => '',
166              
167             };
168              
169 37         91 Hub::opts( \@_, $opts );
170              
171 37 50       86 $$opts{'ref'} and $$opts{'test'} = 'ref';
172              
173 37 50       74 $$opts{'isa'} and $$opts{'test'} = 'isa';
174              
175 37         55 my ($opt,$val) = ('','');
176              
177 37 50       110 ($opt,$val) = $$opts{'test'} =~ /^(\w+)=(.*)/ and do {
178              
179 0         0 $$opts{$opt} = $val;
180              
181 0         0 $$opts{'test'} = $opt;
182              
183             };
184              
185 37 0       79 my $ok = $$opts{'opr'} eq 'and' ? 1 : $$opts{'opr'} eq 'or' ? 0 : undef;
    50          
186              
187 37         92 for( my $i = 0; $i <= $#_; $i++ ) {
188              
189 37         43 my $result = 0;
190              
191             # Test item
192              
193 37 50       83 $$opts{'test'} eq 'def' and $result = defined $_[$i];
194              
195 37 50       80 $$opts{'test'} eq 'num' and $result = $_[$i] =~ EXPR_NUMERIC;
196              
197 37 50       72 $$opts{'test'} eq 'str' and $result = $_[$i] !~ EXPR_NUMERIC;
198              
199 37 50       76 $$opts{'test'} eq 'match' and $result = $_[$i] =~ /$$opts{'match'}/;
200              
201 37 50       192 $$opts{'test'} eq 'blessed' and $result = blessed( $_[$i] ) ? 1 : 0;
    50          
202              
203 37 50       95 $$opts{'test'} eq 'isa' and $result = isa($_[$i], $$opts{'isa'});
204              
205 37 50       81 $$opts{'test'} eq 'ref' and do {
206            
207 0 0 0     0 if( ref($_[$i]) && $$opts{'ref'} ) {
208            
209 0         0 $result = scalar($_[$i]) =~ $$opts{'ref'};
210              
211             }#if
212              
213             };
214              
215 37 50       120 $$opts{'test'} eq 'eval' and do {
216              
217 1     1   7 no warnings; # useless use of eval return
  1         2  
  1         6218  
218              
219 0         0 ref($_[$i]) eq 'CODE' ? eval &{ $_[$i] }
220 0 0       0 : !ref($_[$i]) ? eval { "$_[$i]" }
  0 0       0  
221             : croak 'Cannot eval: $_[$i]';
222              
223 0         0 $result = !$@;
224              
225             };
226              
227             # Assign result
228              
229 37 50       94 $ok &= $result if( $$opts{'opr'} eq 'and' );
230              
231 37 50       74 $ok |= $result if( $$opts{'opr'} eq 'or' );
232              
233 37 50       138 if( $$opts{'opr'} eq 'xor' ) {
234              
235 0 0       0 if( ($i % 2) == 0 ) {
236              
237 0         0 $ok = $result;
238              
239 0         0 next;
240              
241             } else {
242              
243 0         0 $ok ^= $result;
244              
245             }#if
246              
247             }#if
248              
249 37 50       168 last unless $ok;
250              
251             }#for
252              
253 37         287 return $ok;
254              
255             }#check
256              
257             # ------------------------------------------------------------------------------
258             # opts [OPTIONS], \ARRAY, [\HASH]
259             #
260             # Split parameter arrays into options and arguments.
261             #
262             # OPTIONS:
263             #
264             # -prefix=EXPR # specify option prefix, default is single dash (-).
265             #
266             # -assign=EXPR # specify assignment character, default is the
267             # equal sign (=).
268             #
269             # -append=EXPR # specify append character, default is the
270             # plus sign (+).
271             #
272             # ------------------------------------------------------------------------------
273             #
274             # In array context, we return two references. Which may cause confusion:
275             #
276             # my %opts = Hub::opts( \@_ ); # Wrong!
277             # my $opts = Hub::opts( \@_ ); # Correct!
278             # my ($opts,$args) = Hub::opts( \@_ ); # Correct!
279             #
280             # ------------------------------------------------------------------------------
281             #
282             # Options are extracted (via splice) from the referenced array. The advantage
283             # is both for performance (don't make a copy of the array), and so you may
284             # use @_ (or @ARGV, etc) normally, as data:
285             #
286             #|test(match,a;b;c;d) # at-underscore contains everyting but the '-with' option
287             #|
288             #| sub myjoin {
289             #| my $opts = Hub::opts( \@_ );
290             #| return join( $$opts{'with'}, @_ );
291             #| }
292             #|
293             #| myjoin( 'a', 'b', '-with=;', 'c', 'd' );
294             #
295             # ------------------------------------------------------------------------------
296             #
297             # 1. Arguments are elements which do *not* begin with a dash (-).
298             #
299             # 2. Options are elements which begin with a B dash (-) and are not
300             # negative numbers.
301             #
302             # 3. An option of '-opts' is reserved for passing in already parsed option
303             # hashes.
304             #
305             # 4. Options will have their leading dash (-) removed.
306             #
307             # 5. Options values are formed as:
308             #
309             # Given: opt1 will be: because:
310             #
311             # -opt1=value 'value' contains an equal sign
312             # -opt1 nextelem 'nextelem' next element is *not* an option
313             # -opt1 -option2 1 next element is also an option
314             # -opt1 1 it is the last element
315             # -opt1 1 it is the last element
316             # -opt1=a -opt1=b b last one wins
317             # -opt1=a +opt1=b [ 'a', 'b' ] it was specified using '+'
318             # +opt1=a +opt1=b [ 'a', 'b' ] they can both be '+'
319             #
320             # ------------------------------------------------------------------------------
321             #
322             # For example:
323             #
324             # my($opts,$args) = Hub::opts( [ 'a', 'b', '-c' => 'c', '-x', '-o=out' ] );
325             #
326             # print "Opts:\n", Hub::hprint( $opts );
327             # print "Args:\n", Hub::hprint( $args );
328             #
329             # Will print:
330             #
331             # Opts:
332             # c => c
333             # o => out
334             # x => 1
335             # Args:
336             # a
337             # b
338             #
339             # ------------------------------------------------------------------------------
340              
341             sub opts {
342 186     186 1 661 my $opts = {
343             'append' => '\+',
344             'prefix' => '-',
345             'assign' => '=',
346             };
347 186         278 my $argv = shift;
348 186 100       403 my $options = ref($_[0]) eq 'HASH' ? shift : {};
349 186         275 my @remove = ();
350 186 100       377 Hub::opts(\@_,$opts) if @_;
351 186 50 33     908 croak "Provide an array reference" if defined $argv && not isa($argv, 'ARRAY');
352 186 100 66     1022 return $options unless defined $argv && @$argv;
353 153         359 for( my $idx = 0; $idx <= $#$argv; $idx++ ) {
354 274 100       882 next unless defined $$argv[$idx];
355 265 100       594 next if ref( $$argv[$idx] );
356 211 100       1694 if( my($prefix,$k) =
357             $$argv[$idx] =~/^($$opts{'append'}|$$opts{'prefix'})((?!\d|$$opts{'prefix'}).*)$/ ) {
358 141 50       266 next unless $k;
359 141 50       683 if( $k eq 'opts' ) {
    100          
    100          
360 0 0       0 Hub::merge( $options, $$argv[$idx+1], -overwrite => 1 )
361             if defined $$argv[$idx+1];
362 0         0 push @remove, ($idx, $idx+1);
363             } elsif( $k =~ /$$opts{'assign'}/ ) {
364 47         325 my ($k2,$v) = $k =~ /([^$$opts{'assign'}]+)?$$opts{'assign'}(.*)/;
365 47         108 _assignopt( $opts, $options, $k2, $v, $prefix );
366 47         181 push @remove, $idx;
367             } elsif( $idx < $#$argv ) {
368 86 50 33     763 if( !defined $$argv[$idx+1]
      33        
369             || ( (defined $$argv[$idx+1])
370             && $$argv[$idx+1] !~ /^($$opts{'append'}|$$opts{'prefix'})(?!\d)/) ) {
371 86         486 _assignopt( $opts, $options, $k, $$argv[++$idx], $prefix );
372 86         342 push @remove, ( ($idx-1), $idx );
373             } else {
374 0         0 _assignopt( $opts, $options, $k, 1, $prefix );
375 0         0 push @remove, $idx;
376             }
377             } else {
378 8         19 _assignopt( $opts, $options, $k, 1, $prefix );
379 8         29 push @remove, $idx;
380             }
381             }
382             }
383 153         190 my $offset = 0;
384 153         205 map { splice @$argv, $_ - $offset++, 1 } @remove;
  227         382  
385 153 100       340 wantarray and return ($options,@$argv);
386 136         379 return $options;
387             }#opts
388              
389             # ------------------------------------------------------------------------------
390             # objopts - Split @_ into ($self,$opts), leaving @_ with remaining items.
391             # objopts \@params, [\%defaults]
392             #
393             # Convienence method for splitting instance method parameters.
394             # Returns an array.
395             # ------------------------------------------------------------------------------
396             #|test(match) # Test return value
397             #|
398             #| my $obj = mkinst( 'Object' );
399             #| my @result = objopts( [ $obj ] );
400             #| join( ',', map { ref($_) } @result );
401             #|
402             #=Hub::Base::Object,
403             # ------------------------------------------------------------------------------
404              
405             sub objopts {
406 36     36 1 48 my $params = shift;
407 36         46 my $defaults = shift;
408 36         52 my $self = $$params[0]; # not shifted
409 36         41 shift @$params;
410 36         130 Hub::expect(-blessed => $self, -back => 1);
411 36 100       108 my $opts = Hub::opts($params, $defaults) if @$params;
412 36         130 return($self, $opts, @$params);
413             }#objopts
414              
415             # ------------------------------------------------------------------------------
416             # cmdopts - Extract short and long options from @ARGV
417             # cmdopts \@arguments
418             # cmdopts \@arguments, \%default_options
419             #
420             # Single-dash paramaters are always boolean flags. Flags are broken apart such
421             # that:
422             #
423             # -lal
424             #
425             # becomes
426             #
427             # -l -a -l
428             #
429             # To create a list (ARRAY) of items, use '++' where you would normally use '--'.
430             # ------------------------------------------------------------------------------
431             #|test(match,a-b-c)
432             #| my $opts = cmdopts(['--letters=a', '++letters=b', '++letters=c']);
433             #| join('-', @{$$opts{'letters'}});
434             # ------------------------------------------------------------------------------
435              
436             sub cmdopts {
437 1     1 1 2 my $argv = shift;
438 1         2 my @flags = ();
439             # Parse-out flags (single-dash parameters)
440 1         1 my $i = 0;
441 1         6 for (my $i = 0; $i < @$argv;) {
442 0         0 my $arg = $$argv[$i];
443 0 0       0 if ($arg =~ /^-\w/) {
444 0         0 push @flags, $arg =~ /(\w)/g;
445 0         0 splice @$argv, $i, 1;
446             } else {
447 0         0 $i++;
448             }
449             }
450             # Parse double-dash parameters
451 1         5 my $result = Hub::opts( $argv, @_, '-prefix=-{2}', '-append=\+{2}' );
452             # Inject flags in final result
453 1         3 foreach my $flag (@flags) {
454 0 0       0 $result->{$flag} = defined $$result{$flag} ? $$result{$flag} + 1 : 1;
455             }
456 1         7 return $result;
457             }#cmdopts
458              
459             # ------------------------------------------------------------------------------
460             # hashopts - Get options and parameters as a hash
461             # hashopts \@parameters
462             #
463             # The purpose of this method is to even out the returned parameter list by
464             # adding an undefined value if there are an odd number of elements in the list.
465             # This avoids the Perl warning:
466             #
467             # Odd number of elements in hash assignment
468             #
469             # When parsing options as:
470             #
471             # my ($opts, %fields) = Hub::opts(...)
472             # ------------------------------------------------------------------------------
473             #|test(!defined)
474             #| my ($opts, %hash) = Hub::hashopts(['key1', -foo]);
475             #| $hash{'key1'}
476             # ------------------------------------------------------------------------------
477              
478             sub hashopts {
479 0     0 1 0 my ($opts, @fields) = Hub::opts(@_);
480 0 0       0 push @fields, undef if ((scalar (@fields) % 2) != 0);
481 0         0 return ($opts, @fields);
482             }#hashopts
483              
484             # ------------------------------------------------------------------------------
485             # _assignopt Assign an option value.
486             # _assignopt \%options, \%dest, $key, $val
487             # ------------------------------------------------------------------------------
488              
489             sub _assignopt {
490 141     141   186 my $opts = $_[0];
491 141 50       487 if( $_[4] !~ /^$$opts{'append'}$/ ) {
492 141         305 $_[1]->{$_[2]} = $_[3];
493 141         260 return;
494             };
495 0 0       0 if( defined $_[1]->{$_[2]} ) {
496 0 0       0 if( ref($_[1]->{$_[2]}) eq 'ARRAY' ) {
497 0         0 push @{$_[1]->{$_[2]}}, $_[3];
  0         0  
498             } else {
499 0         0 my $v = $_[1]->{$_[2]};
500 0         0 $_[1]->{$_[2]} = [ $v, $_[3] ];
501             }
502             } else {
503 0         0 push @{$_[1]->{$_[2]}}, $_[3];
  0         0  
504             # $_[1]->{$_[2]} = $_[3];
505             }
506             }#_assignopt
507              
508             # ------------------------------------------------------------------------------
509             # subst
510             #
511             # Call to perl's substitution operator. Represented here as a function to
512             # facilitate transformation by reducing the need for temporaries. In essence,
513             # the goal is to reduce:
514             #
515             # my $bakname = getfilename();
516             # $bakname =~ s/\.db$/\.bak/;
517             #
518             # to:
519             #
520             # my $bakname = Hub::subst( getfilename(), '\.db$', '.bak' );
521             #
522             # without modifying the original string returned by getfilename().
523             # ------------------------------------------------------------------------------
524              
525             sub subst {
526 0     0 1 0 my ($s,$l,$r,$m) = @_;
527             # s string to operate on
528             # l left-half of s/// operation
529             # r right-half of s/// operation
530             # m modifier for s/// operation
531 0 0       0 return '' unless Hub::check( $s, $l, $r );
532 0 0       0 ref($s) eq 'SCALAR' and $s = $$s;
533 0   0     0 $m ||= '';
534 0         0 eval( "\$s =~ s/$l/$r/$m" );
535 0 0       0 croak $@ if $@;
536 0         0 return $s;
537             }#subst
538              
539             # ------------------------------------------------------------------------------
540             # getuid - Return the UID of the provided user
541             # getuid $user_name
542             # If perl has not been compiled with 'getpwnam', $user_name is returned.
543             # -1 is returned when no user is found
544             # ------------------------------------------------------------------------------
545              
546             sub getuid {
547 0 0   0 1 0 return $_[0] if Hub::check($_[0], -test => 'num');
548 0 0       0 if ($HAS_GETPWNAM) {
549 0 0       0 my ($login,$pass,$uid,$gid) = getpwnam($_[0]) or return -1;
550 0         0 return $uid;
551             } else {
552 0         0 return $_[0];
553             }
554             }#getuid
555              
556             # ------------------------------------------------------------------------------
557             # getgid - Return the GID of the provided group
558             # getgid - $group_name
559             # If perl has not been compiled with 'getgrnam', $group_name is returned.
560             # -1 is returned when no group is found
561             # ------------------------------------------------------------------------------
562              
563             sub getgid {
564 0 0   0 1 0 return $_[0] if Hub::check($_[0], -test => 'num');
565 0 0       0 if ($HAS_GETGRNAM) {
566 0 0       0 my ($name,$passwd,$gid,$members) = getgrnam($_[0]) or return -1;
567 0         0 return $gid;
568             } else {
569 0         0 return $_[0];
570             }
571             }#getgid
572              
573             # ------------------------------------------------------------------------------
574             # touch LIST
575             #
576             # Changes the access and modification times on each file of a list of files.
577             # ------------------------------------------------------------------------------
578              
579             sub touch {
580 0 0   0 1 0 map { Hub::writefile( $_, '' ) unless -e $_ } @_;
  0         0  
581 0         0 my $t = time;
582 0         0 utime $t, $t, @_;
583             }#touch
584              
585             # ------------------------------------------------------------------------------
586             # expect - Croak if arguments do not match their expected type
587             # expect [OPTIONS], [TEST], LIST
588             #
589             # OPTIONS:
590             #
591             # -back \d # Carp level (for reporting further up the callstack)
592             # -not 0|1 # Invert the result
593             #
594             # TESTS:
595             #
596             # -blessed # All LIST items are blessed
597             # -match=EXPR # All LIST items match /EXPR/
598             # -ref=EXPR # All LIST items' ref match /EXPR/
599             #
600             # By default, LIST is made up of key/value pairs, where the key is the type
601             # (what ref() will return) and the value is what will be tested. LIST may
602             # contain one or more key/value pairs such as:
603             #
604             # HASH => arg
605             # REF => arg
606             # My::Package => arg
607             # ------------------------------------------------------------------------------
608             #|test(true) Hub::expect( -match => 'and|or|xor', 'and' );
609             #|test(true) Hub::expect( HASH => {}, HASH => {} );
610             #|test(abort) Hub::expect( -blessed => {} );
611             #|test(true) Hub::expect( -blessed => mkinst( 'Object' ) );
612             #|test(abort) Hub::expect( -match => 'and|or|xor', 'if', 'or', 'and' );
613             #|test(abort) Hub::expect( ARRAY => {} );
614             #|test(abort) Hub::expect( -blessed => 'abc' );
615             #|test(true) Hub::expect( -ref => 'HASH', {} );
616             #|test(true) Hub::expect( -ref => 'HASH', mkinst('Object') );
617             # ------------------------------------------------------------------------------
618              
619             sub expect {
620 36     36 1 81 my $opts = Hub::opts( \@_ );
621 36 50       84 my $invert = defined $$opts{'not'} ? 1 : 0;
622 36         50 delete $$opts{'not'};
623 36   50     93 my $back = $$opts{'back'} || 0;
624 36 50       89 if( $$opts{'match'} ) {
    50          
    0          
625 0 0 0     0 abort( -back => $back, -msg => "Expected: $$opts{'match'}" )
626             unless( Hub::check( "-test=match=$$opts{'match'}", @_ )
627             xor $invert );
628 0         0 @_ = ();
629             } elsif( $$opts{'blessed'} ) {
630 36 50 25     123 abort( -back => $back, -msg => "Expected: blessed" )
631             unless( Hub::check( "-test=blessed", $$opts{'blessed'}, @_ )
632             xor $invert );
633             } elsif( $$opts{'ref'} ) {
634 0 0 0     0 abort( -back => $back, -msg => "Expected: hashable" )
635             unless( Hub::check( "-ref=$$opts{'ref'}", @_ )
636             xor $invert );
637             } else {
638 0         0 while( my ($k,$v) = (shift,shift) ) {
639 0 0       0 last unless defined $k;
640 0 0       0 abort( -back => $back, -msg => "Expected: '$k', got '"
    0          
641             . ref($v) . "'" )
642             if( $invert ? ref($v) eq $k : ref($v) ne $k );
643             }
644             }
645 36         86 1;
646             }#expect
647              
648             # ------------------------------------------------------------------------------
649             # Croak if arguments match their feared type.
650             # This is a shortcut to L with a '-not=1' option.
651             # ------------------------------------------------------------------------------
652             #|test(abort) Hub::fear( HASH => {} );
653             #|test(true) Hub::fear( HASH => [] );
654             # ------------------------------------------------------------------------------
655              
656             sub fear {
657 0     0 1 0 return Hub::expect( '-not=1', @_ );
658             }#fear
659              
660             # ------------------------------------------------------------------------------
661             # abort - Croak nicely.
662             # abort -msg => 'Croak message'
663             # abort -back => LEVEL
664             #
665             # ------------------------------------------------------------------------------
666             #|test(abort) abort( -msg => 'Goddamn hippies' );
667             # ------------------------------------------------------------------------------
668              
669             sub abort {
670 0     0 1 0 my $opts = Hub::opts(\@_);
671 0   0     0 $$opts{'msg'} ||= $@;
672 0   0     0 $$opts{'msg'} ||= $!;
673 0 0       0 $$opts{'back'} = 1 unless defined $$opts{'back'};
674 0         0 $Carp::CarpLevel = $$opts{'back'};
675 0         0 croak $$opts{'msg'};
676             }#abort
677              
678             # ------------------------------------------------------------------------------
679             # bestof @list
680             # bestof @list, -by=max|min|def|len|gt|lt|true
681             #
682             # Best value by criteria (default 'def').
683             # ------------------------------------------------------------------------------
684              
685             sub bestof {
686 31     31 1 84 my $opts = Hub::opts( \@_ );
687 31   50     167 $$opts{'by'} ||= 'def';
688 31         109 my $best = $_[0];
689 31         85 for( my $i = 1; $i <= $#_; $i++ ) {
690 31 100       59 if( not defined $best ) {
691 1         4 $best = $_[$i];
692 1 50 33     15 ($$opts{'by'} eq 'def') && (defined $best) and last;
693 0         0 next;
694             }
695 30 50 33     122 if( defined $_[$i] && defined $best ) {
696 30         44 my $isbetter = 0;
697 30 50       86 $$opts{'by'} eq 'gt' and $isbetter = $_[$i] gt $best;
698 30 50       62 $$opts{'by'} eq 'lt' and $isbetter = $_[$i] lt $best;
699 30 50 33     79 $$opts{'by'} eq 'max' and Hub::check( '-test=num', $_[$i], $best )
700             and $isbetter = $_[$i] > $best;
701 30 50 33     79 $$opts{'by'} eq 'min' and Hub::check( '-test=num', $_[$i], $best )
702             and $isbetter = $_[$i] < $best;
703 30 50       64 $$opts{'by'} eq 'len' and $isbetter = length($_[$i]) > length($best);
704 30 0 0     64 $$opts{'by'} eq 'true' and $isbetter =
    0 0        
    50          
705             defined $best && $best
706             ? 0 # should call 'last' here
707             : defined $_[$i] && $_[$i]
708             ? 1
709             : 0;
710 30 50       101 $isbetter and $best = $_[$i];
711             }
712             }
713 31         199 return $best;
714             }#bestof
715              
716             # ------------------------------------------------------------------------------
717             # min - Minimum value
718             #
719             # min @LIST
720             #
721             # Returns the least element in a set.
722             # ------------------------------------------------------------------------------
723             #|test(match,1) Hub::min(1,2); # Two integers
724             #|test(match,1) Hub::min(2,1,3); # Three integers
725             #|test(match,-1) Hub::min(2,-1,3); # Three integers
726             #|test(match,1) Hub::min(1); # One integer
727             #|test(match,1) Hub::min(1,undef); # Undefined value
728             #|test(match,0) Hub::min(undef,1,0); # Zero
729             #|test(match,0.009) Hub::min(.009,1.001); # Three decimal values
730             # ------------------------------------------------------------------------------
731              
732             sub min {
733 0     0 1 0 return Hub::bestof( -by => 'min', @_ );
734             }#min
735              
736             # ------------------------------------------------------------------------------
737             # max - Maximum value
738             #
739             # max @LIST
740             #
741             # Returns the greatest element in a set.
742             # ------------------------------------------------------------------------------
743             #|test(match,2) Hub::max(.009,-1.01,2,undef,0); # Three decimal values
744             # ------------------------------------------------------------------------------
745              
746             sub max {
747 0     0 1 0 return Hub::bestof( -by => 'max', @_ );
748             }#max
749              
750             # ------------------------------------------------------------------------------
751             # intdiv - Integer division
752             #
753             # intdiv $DIVIDEND, $DIVISOR
754             #
755             # Returns an array with the number of times the divisor is contained in the
756             # dividend, and the remainder.
757             # ------------------------------------------------------------------------------
758             #|test(match) join(',',Hub::intdiv(3,2)); # 3 divided by 2 is 1R1
759             #=1,1
760             # ------------------------------------------------------------------------------
761              
762             sub intdiv {
763 0     0 1 0 my ($dividend,$divisor) = @_;
764 0 0       0 return( undef, undef ) if $divisor == 0;
765 0         0 return( int( $dividend / $divisor ), ( $dividend % $divisor ) );
766             }#intdiv
767              
768             # ------------------------------------------------------------------------------
769             # given a hash reference, swap keys with values and return a new hash reference.
770             # ------------------------------------------------------------------------------
771              
772             sub flip {
773 0   0 0 1 0 my $hash = shift || return undef;
774 0         0 my $new_hash = {};
775 0 0       0 if (isa($hash, 'HASH')) {
776 0         0 keys %$hash; # reset
777 0         0 while (my ($k,$v) = each %$hash) {
778 0 0       0 if ($$new_hash{$v}) {
779 0 0       0 $$new_hash{$v} = [$$new_hash{$v}] unless isa($$new_hash{$v}, 'ARRAY');
780 0         0 push @{$$new_hash{$v}}, $k;
  0         0  
781             } else {
782 0         0 $$new_hash{$v} = $k;
783             }
784             }
785             }
786 0         0 return $new_hash;
787             }#flip
788              
789             # ------------------------------------------------------------------------------
790             # rmval - Remove matching elements from a hash or an array.
791             # rmval \@array, $value
792             # rmval \%hash, $value
793             # ------------------------------------------------------------------------------
794             #|test(match,124) join('',@{rmval([1,2,3,4],3)});
795             # ------------------------------------------------------------------------------
796              
797             sub rmval {
798 0     0 1 0 my ($container, $value) = @_;
799 0 0       0 if (isa($container, 'HASH')) {
    0          
800 0         0 foreach my $key ( keys %$container ) {
801 0 0       0 if( $$container{$key} eq $value ) {
802 0         0 delete $$container{$key};
803             }
804             }
805             } elsif (isa($container, 'ARRAY')) {
806 0         0 my $index = 0;
807 0         0 foreach my $item (@$container) {
808 0 0       0 if ($item eq $value) {
809 0         0 splice @$container, $index, 1;
810             # keep going
811             } else {
812 0         0 $index++;
813             }
814             }
815             } else {
816 0         0 croak "Cannot remove value from the provided container.";
817             }
818 0         0 return $container;
819             }#rmval
820              
821             # ------------------------------------------------------------------------------
822             # cpref - Recursively clone the reference, returning a new reference.
823             # cpref ?ref
824             # Implemented because the Clone module found on CPAN crashes under my mod_perl
825             # and FastCGI test servers...
826             # ------------------------------------------------------------------------------
827              
828             sub cpref {
829 1     1 1 2 my $ref = shift;
830 1         3 my $new = ();
831 1 50       4 return $ref unless ref($ref);
832 1 50       6 if (isa($ref, 'HASH')) {
    0          
    0          
    0          
    0          
833 1 50       6 $new = blessed $ref ? ref($ref)->new() : {};
834 1         2 keys %$ref; # reset iterator
835 1         8 while( my($k,$v) = each %$ref ) {
836 22 50       48 if( ref($v) ) {
837 0 0       0 $new->{$k} = cpref($v) unless $v eq $ref;
838             } else {
839 22         106 $new->{$k} = $v;
840             }
841             }
842             } elsif (isa($ref, 'ARRAY')) {
843 0 0       0 $new = blessed $ref ? ref($ref)->new() : [];
844 0         0 foreach my $v ( @$ref ) {
845 0 0       0 if( ref($v) ) {
846 0         0 push @$new, cpref($v);
847             } else {
848 0         0 push @$new, $v;
849             }
850             }
851             } elsif (isa($ref, 'SCALAR')) {
852 0         0 my $tmp = $$ref;
853 0         0 $new = \$tmp;
854             } elsif (ref($ref) eq 'REF') {
855 0 0       0 $$ref eq $ref and
856             warn "Self reference cannot be copied: $ref";
857 0 0       0 ($$ref ne $ref) and $new = cpref($$ref);
858             } elsif (ref($ref) eq 'CODE') {
859 0         0 $new = $ref;
860             } else {
861 0         0 croak "Cannot copy reference: $ref\n";
862             }
863 1         7 return $new;
864             }#cpref
865              
866             # ------------------------------------------------------------------------------
867             # random_id - Get a random numeric value for use as an id
868             # random_id
869             #
870             # Creates a checksum of the current time() plus 4 digit rand() number.
871             # ------------------------------------------------------------------------------
872              
873             sub random_id {
874 0     0 1   return Hub::checksum(sprintf("%d%03d", time(), int(rand()*1000)));
875             }#random_id
876              
877             # ------------------------------------------------------------------------------
878             # checksum - Create a unique identifier for the provided data
879             # checksum [params..]
880             # Params can be scalars, hash references, array references and the like.
881             # ------------------------------------------------------------------------------
882             #|test(match)
883             #|
884             #| my $x = 'like catfood';
885             #| Hub::checksum( 'my', { cats => 'breath' }, ( 'smells', $x ) );
886             #|
887             #~ 2023611966
888             # ------------------------------------------------------------------------------
889              
890             sub checksum {
891 0     0 1   my $buffer = "";
892 0           foreach my $param ( @_ ) {
893 0 0         if( ref($param) eq 'HASH' ) {
    0          
    0          
    0          
894 0           $buffer .= Hub::flatten( $param );
895             } elsif( ref($param) eq 'ARRAY' ) {
896 0           $buffer .= Hub::checksum( @$param );
897             } elsif( ref($param) eq 'SCALAR' ) {
898 0           $buffer .= $$param;
899             } elsif( ref($param) eq "Fh" ) {
900 0 0         $param =~ /(.*)/ and $buffer .= $1;
901             } else {
902 0           $buffer .= $param;
903             }#if
904             }#foreach
905 0           my $crc32 = crc32($buffer); # crc32 is faster than adler32
906 0           return $crc32;
907             }#checksum
908              
909             # ------------------------------------------------------------------------------
910             # merge - Merge several hashes
911             # merge \%target, \%source, [\%source..], [options]
912             # returns \%hash
913             #
914             # Merges the provided hashes. The first argument (destination hash) has
915             # precedence (as in values are NOT overwritten) unless -overwrite is given.
916             #
917             # By default this routine modifies \%target. Specifiy -copy circumvent.
918             #
919             # OPTIONS:
920             #
921             # -copy Do not modify \%target.
922             #
923             # -overwrite=1 Overwrite values as they are encounterred.
924             #
925             # -prune=1 Gives the destination hash the same structure as
926             # the source hash (or the composite of all which is
927             # in common when multiple source hashes are provided).
928             #
929             # If the destination is missing a value, it is
930             # initialized from the source hash.
931             #
932             # If the destination has a value which is not in all
933             # of the source hashes, it is deleted.
934             # ------------------------------------------------------------------------------
935              
936             sub merge {
937 0     0 1   my ($opts) = Hub::opts(\@_, {
938             'overwrite' => 0,
939             'prune' => 0,
940             'copy' => 0,
941             });
942 0           my $target = shift; # destination hash
943 0 0         $target = {} unless defined $target;
944 0 0         my $dh = $$opts{'copy'} ? Hub::cpref($target) : $target;
945 0 0         return unless isa($dh, 'HASH');
946 0           foreach my $sh ( @_ ) {
947 0           _merge_hash($dh, $sh, $opts);
948             }
949 0           return $dh;
950             }#merge
951              
952             sub _merge_hash {
953 0     0     my ($dh,$sh,$opts) = @_;
954 0 0         if ($$opts{'prune'}) {
955 0           my @d_keys = keys %$dh;
956 0           foreach my $k ( @d_keys ) {
957 0 0         delete $$dh{$k} unless defined $$sh{$k};
958             }
959             }
960 0           keys %$sh; # reset iterator
961 0           while( my($k,$v) = each %$sh ) {
962 0           _merge_element( $dh, $k, $v, $opts );
963             }
964             }#_merge_hash
965              
966             sub _merge_array {
967 0     0     my ($da,$sa,$opts) = @_; # destination array, source array
968 0           for (my $i = 0; $i < @$sa; $i++) {
969 0 0         if (defined $$sa[$i]) {
970 0 0 0       if (isa($$da[$i], 'HASH') && isa($$sa[$i], 'HASH')) {
    0 0        
    0 0        
971 0           _merge_hash($$da[$i], $$sa[$i], $opts);
972             } elsif (isa($$da[$i], 'ARRAY') && isa($$sa[$i], 'ARRAY')) {
973 0           _merge_array($$da[$i], $$sa[$i], $opts);
974             } elsif (!exists $$da[$i] || $$opts{'overwrite'}) {
975 0           $$da[$i] = $$sa[$i];
976             }
977             }
978             }
979             }#_merge_array
980              
981             sub _merge_element {
982 0     0     my ($dh, $k, $v, $opts) = @_;
983 0 0         if (defined($$dh{$k})) {
984 0 0 0       if (isa($$dh{$k}, 'HASH') && isa($v, 'HASH')) {
    0 0        
985 0           _merge_hash($$dh{$k}, $v, $opts);
986             } elsif (isa($$dh{$k}, 'ARRAY') && isa($v, 'ARRAY')) {
987 0           _merge_array($$dh{$k}, $v, $opts);
988             } else {
989             # do not chage the type (unless overwriting)
990 0 0         $$opts{'overwrite'} and $$dh{$k} = $v;
991             }
992             } else {
993 0           my $vcopy = Hub::cpref($v);
994 0 0         $$dh{$k} = defined($vcopy) ? $vcopy : '';
995             }
996             }#_merge_element
997              
998             # ------------------------------------------------------------------------------
999             # flatten - Get a consistent unique-by-data string for some data structure.
1000             # flatten \%hash
1001             # flatten \%array
1002             # ------------------------------------------------------------------------------
1003              
1004             sub flatten {
1005 0   0 0 1   my $ptr = shift || return;
1006 0           my $buf = "";
1007 0 0         if (isa($ptr, 'HASH')) {
    0          
1008 0           foreach my $k ( sort keys %$ptr ) {
1009 0           my $v = $$ptr{$k};
1010 0 0         if( ref($v) ) {
1011 0           $buf .= $k;
1012 0           $buf .= Hub::flatten( $v );
1013             } else {
1014 0 0 0       if( !$k || $v =~ /\n/ ) {
1015 0           $buf .= $v;
1016             } else {
1017 0           $buf .= $k . $v;
1018             }
1019             }
1020             }
1021             } elsif (isa($ptr, 'ARRAY')) {
1022 0           foreach my $v (sort @$ptr) {
1023 0 0         if (ref($v)) {
1024 0           $buf .= Hub::flatten($v);
1025             } else {
1026 0           $buf .= $v;
1027             }
1028             }
1029             } else {
1030 0           die "Cannot flatten structure: $ptr\n";
1031             }
1032 0           return $buf;
1033             }#flatten
1034              
1035             # ------------------------------------------------------------------------------
1036             # replace MATCHING_REGEX, SUBSTITUTION_REGEX, TEXT
1037             #
1038             # Do a s/// operation on a given segment of the string.
1039             #
1040             # For example, say we want to remove the ': ;' pattern from the style portion,
1041             # but not from the data portion:
1042             #
1043             #
keep this: ;stuff
1044             #
1045             # Use this method as:
1046             #
1047             # $text = Hub::replace( "style=\".*?\"", "s/[\\w\\-]+\\s*:\\s*;//g", $text );
1048             #
1049             # ------------------------------------------------------------------------------
1050              
1051             sub replace {
1052              
1053 0     0 1   my ($match,$replace,$str) = @_;
1054              
1055 0 0         return unless $str;
1056              
1057 0           while( $str =~ m/\G.*?($match)/gs ) {
1058              
1059 0           my $substr = $1;
1060              
1061 0           my $beg = pos($str) - length( $substr );
1062              
1063 0 0         if( eval "\$substr =~ $replace" ) {
1064              
1065 0           pos $str = $beg;
1066              
1067 0           $str =~ s/\G$match/$substr/;
1068              
1069 0           pos $str = $beg + length($substr);
1070              
1071             }#if
1072              
1073             }#while
1074              
1075 0           return $str;
1076              
1077             }#replace
1078              
1079             # ------------------------------------------------------------------------------
1080             # digout REF, ID
1081             #
1082             # Return an array of all nested values in an order that can be processed.
1083             #
1084             # NOTE! Scalar values are returned as references.
1085             # See how 'packdata' uses this method to dereference.
1086             #
1087             # Arrays are ignored unless their members are hashes with an _id member.
1088             #
1089             # Reverse the results of this array to process data in a way that the children
1090             # are affected before their parents.
1091             # ------------------------------------------------------------------------------
1092              
1093             sub digout {
1094              
1095 0     0 1   my $r = shift;
1096 0   0       my $id = shift || '';
1097              
1098 0 0         return unless ref($r);
1099              
1100 0           my $h = {};
1101              
1102 0           my $data = [];
1103              
1104 0 0         if( ref($r) eq 'ARRAY' ) {
    0          
1105              
1106 0           foreach my $elem ( @$r ) {
1107              
1108 0 0         if( ref($elem) eq 'HASH' ) {
1109              
1110 0 0         if( $$elem{'_id'} ) {
1111              
1112 0           $h->{$$elem{'_id'}} = $elem;
1113              
1114             }#if
1115              
1116             }#if
1117              
1118             }#foreach
1119              
1120             } elsif( ref($r) eq 'HASH' ) {
1121              
1122 0           $h = $r;
1123              
1124             }#if
1125              
1126 0           foreach my $k ( keys %$h ) {
1127              
1128 0 0         if( ref($h->{$k}) ) {
1129              
1130 0           push @$data, {
1131             key => $k,
1132             id => "$id:$k",
1133             val => $h->{$k},
1134             };
1135              
1136 0           push @$data, @{ &digout($h->{$k},"$id:$k") };
  0            
1137              
1138             } else {
1139              
1140 0           push @$data, {
1141             key => $k,
1142             id => "$id:$k",
1143             val => \$h->{$k},
1144             };
1145              
1146             }#if
1147              
1148             }#foreach
1149              
1150 0           return $data;
1151              
1152             }#digout
1153              
1154             # ------------------------------------------------------------------------------
1155             # diff - Creates a nest of the differences between the provided structures.
1156             # diff \%hash1, \%hash2
1157             # diff \@array1, \@array2
1158             #
1159             # If a conflict of types (with the same key) is encounterred, the right-hand
1160             # sturcture is used.
1161             #
1162             # NOTE: Although this routine compares contents, it returns references to the
1163             # original hashes (use L on the result to detatch.)
1164             # ------------------------------------------------------------------------------
1165              
1166             sub diff {
1167 0     0 1   my ($l,$r) = @_;
1168 0 0         if (isa($l, 'HASH')) {
    0          
1169 0           return _diff_hashes( $l, $r );
1170             } elsif (isa($l, 'ARRAY')) {
1171 0           return _diff_arrays( $l, $r );
1172             }
1173             }#diff
1174              
1175             # ------------------------------------------------------------------------------
1176             # _diff_hashes &HASH, &HASH
1177             #
1178             # Difference between two hashes.
1179             # ------------------------------------------------------------------------------
1180              
1181             sub _diff_hashes {
1182 0     0     my ($l,$r) = @_;
1183 0 0         return unless ref($l) eq 'HASH';
1184 0 0         return unless ref($r) eq 'HASH';
1185 0           my $h = undef;
1186 0           my @lkeys = keys %$l;
1187 0           while( my $key = shift @lkeys ) {
1188 0 0         if( defined $r->{$key} ) {
1189 0 0         if( ref($l->{$key}) eq ref($r->{$key}) ) {
1190 0 0         if( ref($l->{$key}) eq 'HASH' ) {
    0          
1191 0           my $subh = _diff_hashes( $l->{$key}, $r->{$key} );
1192 0 0         $h->{$key} = $subh if $subh;
1193             } elsif( ref($l->{$key}) eq 'ARRAY' ) {
1194 0           my $suba = _diff_arrays( $l->{$key}, $r->{$key} );
1195 0 0         $h->{$key} = $suba if $suba;
1196             } else {
1197 0 0         $h->{$key} = $r->{$key} unless $l->{$key} eq $r->{$key};
1198             }
1199             } else {
1200 0           $h->{$key} = $r->{$key};
1201             }
1202             } else {
1203 0           $h->{$key} = $l->{$key};
1204             }
1205             }
1206 0           my @rkeys = keys %$r;
1207 0           while( my $key = shift @rkeys ) {
1208 0 0         $h->{$key} = $r->{$key} unless defined $l->{$key};
1209             }
1210 0           return $h;
1211             }#_diff_hashes
1212              
1213             # ------------------------------------------------------------------------------
1214             # _diff_arrays &ARRAY, &ARRAY
1215             #
1216             # Difference between two arrays.
1217             # ------------------------------------------------------------------------------
1218              
1219             sub _diff_arrays {
1220 0     0     my ($l,$r) = @_;
1221 0 0         return unless isa($l, 'ARRAY');
1222 0 0         return unless isa($r, 'ARRAY');
1223 0           my $a = undef;
1224 0           my $idx = 0;
1225 0           my $min = Hub::min( $#$l, $#$r );
1226 0           for( my $idx = 0; $idx <= $min; $idx++ ) {
1227 0           my $lval = $l->[$idx];
1228 0           my $rval = $r->[$idx];
1229 0 0         if( ref($lval) eq ref($rval) ) {
1230 0 0         if( ref($lval) eq 'HASH' ) {
    0          
1231 0           my $subh = _diff_hashes( $lval, $rval );
1232 0 0         push( @$a, $subh ) if $subh;
1233             } elsif( ref($rval) eq 'ARRAY' ) {
1234 0           my $suba = _diff_arrays( $lval, $rval );
1235 0 0         push( @$a, $suba ) if $suba;
1236             } else {
1237 0 0         push( @$a, $rval ) unless $lval eq $rval;
1238             }
1239             } else {
1240 0           push @$a, $rval;
1241             }
1242 0           $idx++;
1243             }
1244 0 0         if( $#$l > $#$r ) {
1245 0           foreach my $idx ( ($#$r + 1) .. $#$l ) {
1246 0           push @$a, $l->[$idx];
1247             }
1248             } else {
1249 0           foreach my $idx ( ($#$l + 1) .. $#$r ) {
1250 0           push @$a, $r->[$idx];
1251             }
1252             }
1253 0           return $a;
1254             }#_diff_arrays
1255              
1256             # ------------------------------------------------------------------------------
1257             # dice - Break apart the string into the least number of segments
1258             # dice [options] $string
1259             # options:
1260             # beg=$literal Begin of balanced pair, Default is '{'
1261             # end=$literal End of balanced pair, Default is '}'
1262             # ------------------------------------------------------------------------------
1263             #|test(match,a;{b{c}};c;{d}) join( ';', dice( "a{b{c}}c{d}" ) );
1264             # ------------------------------------------------------------------------------
1265              
1266             sub dice {
1267              
1268 0     0 1   my $opts = {
1269             'beg' => '{',
1270             'end' => '}',
1271             };
1272              
1273 0           Hub::opts( \@_, $opts );
1274 0           my $text = shift;
1275 0           my @result = ();
1276              
1277 0           my %beg = (
1278             str => $$opts{'beg'},
1279             char => substr($$opts{'beg'}, 0, 1),
1280             len => length($$opts{'beg'}),
1281             );
1282              
1283 0           my %end = (
1284             str => $$opts{'end'},
1285             char => substr($$opts{'end'}, 0, 1),
1286             len => length($$opts{'end'}),
1287             );
1288              
1289             # find the beginning
1290 0           my ($p,$p2,$p3) = (0,0,0);
1291 0           while( ($p = index( $text, $beg{'str'}, 0 )) > -1 ) {
1292              
1293             # find the end
1294 0           my $p2 = $p + $beg{'len'}; # start of the current search
1295 0           my $p3 = index( $text, $end{'char'}, $p2 ); # point of closing
1296 0           while( $p3 > -1 ) {
1297 0           my $ic = 0; # inner count
1298 0           my $im = index( $text, $beg{'char'}, $p2 ); # inner match
1299 0   0       while( ($im > -1) && ($im < $p3) ) {
1300 0           $ic++;
1301 0           $p2 = ($im + 1);
1302 0           $im = index( $text, $beg{'char'}, $p2 );
1303             }
1304 0 0         last unless $ic > 0;
1305 0           for( 1 .. $ic ) {
1306 0           $p3 = index( $text, $end{'char'}, ($p3 + 1) );
1307             }
1308             }
1309 0 0         if( $p3 > $p ) {
1310 0           my $str = substr( $text, $p, (($p3 + $end{'len'}) - $p) );
1311 0           my $left = substr( $text, 0, $p );
1312 0           my $right = substr( $text, $p + length($str) );
1313 0           push @result, $left, $str;
1314 0           $text = $right;
1315             } else {
1316 0           croak "Unmatched $beg{'str'}";
1317             }
1318             }
1319              
1320 0 0         $text and push @result, $text;
1321 0           return @result;
1322              
1323             }#dice
1324              
1325             # ------------------------------------------------------------------------------
1326             # indexmatch - Search for an expression within a string and return the offset
1327             # indexmatch [options] $string, $expression, $position
1328             # indexmatch [options] $string, $expression
1329             #
1330             # Returns -1 if $expression is not found.
1331             #
1332             # options:
1333             #
1334             # -after=1 Return the position *after* the expression.
1335             # ------------------------------------------------------------------------------
1336             #|test(match,4) indexmatch("abracadabra", "[cd]")
1337             #|test(match,3) indexmatch("abracadabra", "a", 3)
1338             #|test(match,-1) indexmatch("abracadabra", "d{2,2}")
1339             #|test(match,3) indexmatch("scant", "can", "-after=1")
1340             #| - indexmatch("scant", "can")
1341             # ------------------------------------------------------------------------------
1342              
1343             sub indexmatch {
1344 0     0 1   my ($opts, $str, $expr, $from) = Hub::opts(\@_, {'after' => 0,});
1345 0 0         croak "undefined search string" unless defined $str;
1346 0 0         croak "undefined search expression" unless defined $expr;
1347 0 0         $from = 0 if not defined $from;
1348 0           my $temp_str = substr $str, $from;
1349 0 0         croak "undefined search substring" unless defined $temp_str;
1350 0           my $pos = undef;
1351 0           my @match = $temp_str =~ /($expr)/;
1352 0 0         $pos = index $temp_str, $match[0] if (defined $match[0]);
1353 0 0         return defined $pos
    0          
1354             ? $$opts{'after'}
1355             ? $from + $pos + length($match[0])
1356             : $from + $pos
1357             : -1;
1358             }#indexmatch
1359              
1360             # ------------------------------------------------------------------------------
1361             1;