File Coverage

blib/lib/Proquint.pm
Criterion Covered Total %
statement 76 77 98.7
branch 16 26 61.5
condition 18 51 35.2
subroutine 13 13 100.0
pod 0 6 0.0
total 123 173 71.1


line stmt bran cond sub pod time code
1             package Proquint;
2 1     1   227725 use strict;
  1         3  
  1         28  
3 1     1   5 use warnings;
  1         1  
  1         27  
4 1     1   4 use Carp ();
  1         1  
  1         12  
5 1     1   443 use Exporter::Tiny;
  1         3067  
  1         8  
6 1     1   893 use Socket (qw/inet_pton inet_ntop AF_INET AF_INET6/);
  1         3791  
  1         970  
7              
8             our $VERSION = '1.0.0_3';
9             our @ISA = 'Exporter::Tiny';
10             our @EXPORT_OK = (
11             qw/
12             uint2proquint proquint2uint
13             hex2proquint proquint2hex
14             ip2proquint proquint2ip
15             /
16             );
17             our @EXPORT_TAGS = ( all => \@EXPORT_OK );
18              
19             my @UINT_TO_CONSONANT = (qw/ b d f g h j k l m n p r s t v z /);
20             my @UINT_TO_VOWEL = (qw/ a i o u /);
21             my $CHARS_PER_CHUNK = 5;
22             my $MASK_LAST2 = 0x3;
23             my $MASK_LAST4 = 0xF;
24             my $SEPARATOR = '-';
25              
26             my %CONSONANT_TO_UINT = do {
27             my $i = 0;
28             map { $_ => $i++ } @UINT_TO_CONSONANT;
29             };
30              
31             my %VOWEL_TO_UINT = do {
32             my $i = 0;
33             map { $_ => $i++ } @UINT_TO_VOWEL;
34             };
35              
36             sub _uint16_to_chunk {
37 58   33 58   118 my $in = shift // Carp::croak 'usage: _uint16_to_chunk($INTEGER)';
38 58         83 my $out = '';
39              
40 58         128 foreach my $i ( 1 .. $CHARS_PER_CHUNK ) {
41 290 100       386 if ( $i & 1 ) {
42 174         247 $out .= $UINT_TO_CONSONANT[ $in & $MASK_LAST4 ];
43 174         215 $in >>= 4;
44             }
45             else {
46 116         169 $out .= $UINT_TO_VOWEL[ $in & $MASK_LAST2 ];
47 116         132 $in >>= 2;
48             }
49             }
50 58         281 scalar reverse $out;
51             }
52              
53             sub _chunk_to_uint16 {
54 58   33 58   127 my $in = shift // Carp::croak 'usage: _chunk_to_uint16($INTEGER)';
55              
56 58 50       107 Carp::croak 'invalid chunk: ' . $in unless length($in) == $CHARS_PER_CHUNK;
57              
58 58         69 my $res = 0;
59 58         149 foreach my $c ( split //, $in ) {
60 290 100       497 if ( exists $CONSONANT_TO_UINT{$c} ) {
    50          
61 174         183 $res <<= 4;
62 174         235 $res += $CONSONANT_TO_UINT{$c};
63             }
64             elsif ( exists $VOWEL_TO_UINT{$c} ) {
65 116         126 $res <<= 2;
66 116         142 $res += $VOWEL_TO_UINT{$c};
67             }
68             else {
69 0         0 Carp::croak 'invalid quint char: ' . $c;
70             }
71             }
72              
73 58         249 $res;
74             }
75              
76             # uint2proquint(0x7f000001) eq 'lusab-babad';
77             sub uint2proquint {
78 2   33 2 0 8273 my $in = shift // Carp::croak 'usage: uint2proquint($INTEGER)';
79 2   33     14 my $sep = shift // $SEPARATOR;
80              
81 2 50 33     11 Carp::croak('input out of range 0-0xFFFFFFFF')
82             if $in < 0 or $in > 0xffffffff;
83              
84 2         10 _uint16_to_chunk( $in >> 16 ) . $sep . _uint16_to_chunk($in);
85             }
86              
87             # proquint2uint('lusab-babad') == 0x7f000001;
88             sub proquint2uint {
89 2   33 2 0 10 my $in = shift // Carp::croak 'usage: proquint2uint($QUINT)';
90 2   33     10 my $sep = shift // $SEPARATOR;
91              
92 2         32 $in =~ s/$sep//g;
93 2 50       12 Carp::croak 'invalid quint: ' . $in
94             unless not length($in) % $CHARS_PER_CHUNK;
95              
96 2         28 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/gx;
97 2 50       17 Carp::croak 'invalid quint: ' . $in unless @chunks == 2;
98              
99 2         10 my $out = _chunk_to_uint16( $chunks[0] );
100 2         5 $out <<= 16;
101 2         5 $out += _chunk_to_uint16( $chunks[1] );
102 2         12 $out;
103             }
104              
105             # hex2proquint('7f00001') eq 'lusab-babad'
106             sub hex2proquint {
107 6   33 6 0 10748 my $in = shift // Carp::croak 'usage: hex2proquint($HEXIDECIMAL)';
108 6   33     34 my $sep = shift // $SEPARATOR;
109              
110 6         25 $in =~ s/^0[xX]//;
111              
112 6 50       25 Carp::croak 'input must be multiple of 4-characters'
113             unless not length($in) % 4;
114              
115             join( $sep,
116 6         45 map { _uint16_to_chunk( hex( '0x' . $_ ) ) } $in =~ m/(.{4})/g );
  12         50  
117             }
118              
119             # proquint2hex('lusab-babad') eq '7f000001';
120             sub proquint2hex {
121 6   33 6 0 20 my $in = shift // Carp::croak 'usage: proquint2hex($QUINT)';
122 6   33     24 my $sep = shift // $SEPARATOR;
123              
124 6         49 $in =~ s/$sep//g;
125 6 50       36 Carp::croak 'invalid quint: ' . $in
126             unless not length($in) % $CHARS_PER_CHUNK;
127              
128 6         52 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/g;
129 6 50       24 Carp::croak 'invalid quint: ' . $in unless @chunks;
130              
131 6         17 join( '', map { sprintf( '%04x', _chunk_to_uint16($_) ) } @chunks );
  12         27  
132             }
133              
134             # ip2proquint('127.0.0.1') eq 'lusab-babad'
135             sub ip2proquint {
136 15   33 15 0 26468 my $in = shift // Carp::croak 'usage: ip2proquint($ADDRESS)';
137 15   33     69 my $sep = shift // $SEPARATOR;
138              
139 15   66     171 my $ip = inet_pton( AF_INET6, $in ) // inet_pton( AF_INET, $in )
      33        
140             // Carp::croak sprintf q{invalid IP address '%s'}, $in;
141              
142 15         104 join( $sep, map { _uint16_to_chunk($_) } unpack 'n*', $ip );
  42         78  
143             }
144              
145             # proquint2ip('lusab-babad') eq '127.0.0.1'
146             sub proquint2ip {
147 15   33 15 0 49 my $in = shift // Carp::croak 'usage: proquint2ip($ADDRESS)';
148 15   33     55 my $sep = shift // $SEPARATOR;
149              
150 15         111 $in =~ s/$sep//g;
151 15 50       59 Carp::croak 'invalid quint: ' . $in
152             unless not length($in) % $CHARS_PER_CHUNK;
153              
154 15         123 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/g;
155 15 50       44 Carp::croak 'invalid quint: ' . $in unless @chunks;
156              
157 15         32 my $ip = pack 'n*', map { _chunk_to_uint16($_) } @chunks;
  42         69  
158 15 100       154 return inet_ntop( 2 == @chunks ? AF_INET : AF_INET6, $ip );
159             }
160              
161             1;
162              
163             __END__