Algorithm-ToNumberMunger
view release on metacpan or search on metacpan
t/mungers.t view on Meta::CPAN
is( $pu->('a,b.c!'), 3, 'char punct count' );
my $vo = $M->build( { munger => 'char', class => 'vowel' } );
is( $vo->('Aeixy'), 3, 'char vowel count' );
my $co = $M->build( { munger => 'char', class => 'consonant' } );
is( $co->('Aeixy'), 2, 'char consonant count (y is a consonant)' );
my $xd = $M->build( { munger => 'char', class => 'xdigit' } );
is( $xd->('deadBEEFxyz9'), 9, 'char xdigit count' );
eval { $M->build( { munger => 'char', class => 'bogus' } ) };
like( $@, qr/unknown class 'bogus'/, 'char rejects unknown class' );
eval { $M->build( { munger => 'char', class => 'digit', mode => 'nope' } ) };
like( $@, qr/'mode' must be/, 'char rejects bad mode' );
}
# ---- run --------------------------------------------------------------------
{
my $c = $M->build( { munger => 'run', class => 'consonant' } );
is( $c->('kitchen'), 3, 'run: longest consonant run in a real word' );
is( $c->('xkqvbrtn'), 8, 'run: a DGA-ish string is one long run' );
is( $c->('aeiou'), 0, 'run: no consonants at all' );
is( $c->(''), 0, 'run: empty string' );
is( $c->(undef), 0, 'run: undef is 0' );
my $d = $M->build( { munger => 'run', class => 'digit' } );
is( $d->('ab1234cd56'), 4, 'run: longest digit run' );
eval { $M->build( { munger => 'run' } ) };
like( $@, qr/requires a 'class'/, 'run requires a class' );
eval { $M->build( { munger => 'run', class => 'bogus' } ) };
like( $@, qr/unknown class 'bogus'/, 'run rejects unknown class' );
}
# ---- count ----------------------------------------------------------------
{
my $slashes = $M->build( { munger => 'count', of => '/' } );
is( $slashes->('/a/b/c'), 3, 'count slashes (path depth)' );
is( $slashes->('nopath'), 0, 'count with no matches' );
my $labels = $M->build( { munger => 'count', of => '.', plus => 1 } );
is( $labels->('a.b.evil.com'), 4, 'count dots + 1 (label_count)' );
is( $labels->('localhost'), 1, 'count label_count of a single label' );
# multi-char needles count non-overlapping occurrences (m//g semantics)
my $aa = $M->build( { munger => 'count', of => 'aa' } );
is( $aa->('aaaa'), 2, 'count is non-overlapping' );
is( $aa->('aaaaa'), 2, 'count leftover tail does not match' );
eval { $M->build( { munger => 'count' } ) };
like( $@, qr/non-empty 'of'/, 'count requires of' );
}
# ---- match ------------------------------------------------------------------
{
my $puny = $M->build( { munger => 'match', pattern => '^xn--' } );
is( $puny->('xn--e1afmkfd.ru'), 1, 'match bool: punycode label matches' );
is( $puny->('example.com'), 0, 'match bool: plain label does not' );
is( $puny->(undef), 0, 'match bool: undef is 0' );
my $esc = $M->build( { munger => 'match', pattern => '%[0-9A-Fa-f]{2}', mode => 'count' } );
is( $esc->('/a%20b%2e%2Ec'), 3, 'match count: percent-escapes' );
is( $esc->('/plain/path'), 0, 'match count: none' );
my $ci = $M->build( { munger => 'match', pattern => 'select', ignore_case => 1 } );
is( $ci->('SELECT * FROM'), 1, 'match ignore_case matches across case' );
eval { $M->build( { munger => 'match' } ) };
like( $@, qr/non-empty 'pattern'/, 'match requires a pattern' );
eval { $M->build( { munger => 'match', pattern => '(unclosed' } ) };
like( $@, qr/cannot compile pattern/, 'match croaks on a broken pattern at build time' );
eval { $M->build( { munger => 'match', pattern => 'x', mode => 'nope' } ) };
like( $@, qr/'mode' must be/, 'match rejects bad mode' );
}
# ---- bucket ---------------------------------------------------------------
{
my $port = $M->build( { munger => 'bucket', bounds => [ 1024, 49152 ] } );
is( $port->(80), 0, 'bucket well-known port' );
is( $port->(1023), 0, 'bucket just below first bound' );
is( $port->(1024), 1, 'bucket at first bound is registered' );
is( $port->(8080), 1, 'bucket registered port' );
is( $port->(49152), 2, 'bucket at second bound is ephemeral' );
is( $port->(60000), 2, 'bucket ephemeral port' );
eval { $M->build( { munger => 'bucket', bounds => [] } ) };
like( $@, qr/non-empty 'bounds'/, 'bucket rejects empty bounds' );
eval { $M->build( { munger => 'bucket', bounds => [ 5, 5 ] } ) };
like( $@, qr/strictly ascending/, 'bucket rejects non-ascending bounds' );
}
# ---- quantile ---------------------------------------------------------------
{
# bounds at min/p25/p50/p75/max of an imaginary training column.
my $q = $M->build( { munger => 'quantile', bounds => [ 0, 10, 100, 1000, 10000 ] } );
is( $q->(0), 0, 'quantile at the first bound is 0' );
is( $q->(-5), 0, 'quantile clamps below' );
is( $q->(10000), 1, 'quantile at the last bound is 1' );
is( $q->(999999), 1, 'quantile clamps above' );
is( $q->(10), 0.25, 'quantile at an interior bound' );
is( $q->(100), 0.5, 'quantile at the median bound' );
is( $q->(5), 0.125, 'quantile interpolates within a segment' );
is( $q->(550), 0.625, 'quantile interpolates mid-segment' );
eval { $q->('nope') };
like( $@, qr/not numeric/, 'quantile croaks on non-numeric input' );
eval { $M->build( { munger => 'quantile', bounds => [5] } ) };
like( $@, qr/at least 2/, 'quantile needs two bounds' );
eval { $M->build( { munger => 'quantile', bounds => [ 5, 5 ] } ) };
like( $@, qr/strictly ascending/, 'quantile rejects non-ascending bounds' );
}
# ---- ftp_enum -------------------------------------------------------------
{
my $c = $M->build( { munger => 'ftp_enum' } );
is( $c->(220), 2, 'ftp_enum 2yz -> 2' );
is( $c->(530), 5, 'ftp_enum 5yz -> 5' );
my $s = $M->build( { munger => 'ftp_enum', strict => 1 } );
eval { $s->(700) };
like( $@, qr/out of range \(100-599\)/, 'ftp_enum strict rejects >= 700' );
}
( run in 0.752 second using v1.01-cache-2.11-cpan-7fcb06a456a )