File Coverage

blib/lib/Convert/GeekCode.pm
Criterion Covered Total %
statement 68 98 69.3
branch 21 48 43.7
condition 8 12 66.6
subroutine 10 13 76.9
pod 0 8 0.0
total 107 179 59.7


line stmt bran cond sub pod time code
1             package Convert::GeekCode;
2 1     1   935 use 5.005;
  1         4  
  1         41  
3 1     1   6 use strict;
  1         1  
  1         47  
4 1     1   7 use vars qw/@ISA @EXPORT $VERSION $DELIMITER/;
  1         3  
  1         143  
5              
6             $Convert::GeekCode::VERSION = '0.63';
7              
8 1     1   1215 use YAML ();
  1         11225  
  1         26  
9 1     1   13 use Exporter;
  1         3  
  1         1579  
10              
11             =encoding utf8
12              
13             =head1 NAME
14              
15             Convert::GeekCode - Convert and generate geek code sequences
16              
17             =head1 SYNOPSIS
18              
19             use Convert::GeekCode; # exports geek_decode()
20              
21             my @out = geek_decode(q(
22             -----BEGIN GEEK CODE BLOCK-----
23             Version: 3.12
24             GB/C/CM/CS/CC/ED/H/IT/L/M/MU/P/SS/TW/AT d---x s+: a-- C++++ UB++++$
25             P++++$ L+ E--->+ W+++$ N++ !o K w--(++) O-- M-@ !V PS+++ PE Y+>++
26             PGP++ t+ 5? X+ R+++ !tv b++++ DI+++@ D++ G++++ e-(--) h* r++(+) z++*
27             ------END GEEK CODE BLOCK------
28             )); # yes, that's the author's geek code
29              
30             my ($key, $val);
31             print "[$key]\n$val\n\n" while (($key, $val) = splice(@out, 0, 2));
32              
33             =head1 DESCRIPTION
34              
35             B converts and generates Geek Code sequences (cf.
36             L). It supports different langugage codes and
37             user-customizable codesets.
38              
39             Since version 0.5, this module uses B to represent the geek code
40             tables, for greater readability and ease of deserialization. Please
41             refer to L for more related information.
42              
43             The F and F utilities are installed by default,
44             and may be used to generate / decode geek code blocks, respectively.
45              
46             =cut
47              
48             @ISA = qw/Exporter/;
49             @EXPORT = qw/geek_encode geek_decode/;
50             $DELIMITER = " ";
51              
52             sub new {
53 1     1 0 3 my $class = shift;
54 1   50     6 my $id = shift || 'geekcode';
55 1   50     5 my $version = shift || '3.12';
56 1   50     10 my $lang = shift || 'en_us';
57 1         2 my ( $cursec, $curcode, $curval );
58              
59 1         4 $lang =~ tr/-/_/; # paranoia
60              
61 1 50       20 my $file = locate("$id/$version/$lang.yml")
62             or die "cannot locate $id/$version/$lang.yml in @INC";
63              
64 1         7 my $self = YAML::LoadFile($file);
65 1         443066 return bless( $self, $class );
66             }
67              
68             sub decode {
69 1     1 0 3 my ( $self, $code ) = @_;
70              
71 1 50       60 die "can't find geek code block; stop."
72             unless $code =~ m|\Q$self->{Head}\E([\x00-\xff]+)\Q$self->{Tail}\E|;
73              
74 1         4 $code = $1;
75 1         10 $code =~ s|^\s+||mg; # Strip leading spaces
76 1 50       38 $code =~ s|[\x00-\xff]*?^$self->{Begin}|_|m or die "Sorry, could not decode your Geekcode. Please check if it has the right syntax: http://geekcode.com/\n$code\n";
77              
78 1         3 my @ret;
79              
80 1         29 foreach my $chunk ( split( /[\s\t\n\r]+/, $code ) ) {
81 34 50       146 next unless $chunk =~ m|^(\!?\w+)\b|;
82              
83 34         76 my $head = $1;
84 34         73 while ($head) {
85 38 100       106 if ( exists( $self->{_}{$head} ) ) {
86 33         61 my $sec = $self->{_}{$head};
87 33         36 my $out;
88              
89 33         86 push @ret, $sec->{_};
90 33         68 $chunk = substr( $chunk, length($head) );
91 33 100 66     161 $out = $sec->{''} . $DELIMITER
92             if !$chunk
93             or $chunk =~ /^[\>\(]/;
94              
95 33         91 while ($chunk) {
96 68 100       189 next if $self->tokenize( $sec, \$chunk, \$out );
97 21 50       87 next if $self->tokenize( $self->{_}{''}, \$chunk, \$out );
98              
99 0         0 warn "parse error: ", substr( $chunk, 0, 1 );
100 0         0 $chunk = substr( $chunk, 1 );
101             }
102              
103 33         76 push @ret, $out;
104 33         65 last;
105             }
106              
107 5         14 $head = substr( $head, 0, -1 );
108             }
109             }
110              
111 1         174 return @ret;
112             }
113              
114             sub encode {
115 0     0 0 0 my ( $self, $code ) = @_;
116              
117 0         0 my @out;
118 0         0 foreach my $sec ( split( /[\s\t\n\r]+/, $self->{Sequence} ) ) {
119 0 0       0 my $secref = $self->{_}{$sec} or next;
120 0 0       0 $sec = $self->{Begin} if $sec eq '_';
121 0         0 push @out, $code->(
122             $secref->{_},
123             map {
124 0         0 my $sym = $secref->{$_};
125 0         0 my $code = $_;
126 0         0 $code =~ s/[\x27\/]//g;
127             (
128             (
129 0 0       0 ( index( $code, $sec ) > -1 )
    0          
130             ? $code
131             : ( $code eq '!' ? "$code$sec" : "$sec$code" )
132             ),
133             $sym
134             );
135 0         0 } grep { $_ ne '_' }
136 0         0 sort { calcv($a) cmp calcv($b); } keys( %{$secref} )
  0         0  
137             );
138              
139 0         0 $out[-1] =~ s|\s+|/|g;
140 0         0 $out[-1] =~ s|/+$||;
141 0         0 $out[-1] =~ s|(?<=.)$sec||g;
142             }
143              
144 0         0 return join( "\n",
145             $self->{Head},
146             $self->{Ver} . $self->{Version},
147             join( ' ', @out ),
148             $self->{Tail}, '', );
149             }
150              
151             sub calcv {
152 0 0   0 0 0 my $sym = shift or return '';
153              
154 0 0       0 return chr(0) x ( 10 - length($sym) ) if substr( $sym, 0, 1 ) eq '+';
155 0 0       0 return chr(2) x length($sym) if substr( $sym, 0, 1 ) eq '-';
156 0 0       0 return chr(1) if $sym eq '';
157 0         0 return $sym;
158             }
159              
160             sub tokenize {
161 89     89 0 154 my ( $self, $sec, $chunk, $out ) = @_;
162              
163 89         115 foreach my $key ( sort { length($b) <=> length($a) } keys( %{$sec} ) ) {
  4275         4803  
  89         626  
164 947 100 100     3867 next if $key eq '_' or !$key;
165              
166 906 50       10526 if ( $key =~ m|/(.+)/| ) {
    100          
167 0 0       0 if ( $$chunk =~ s|^$1|| ) {
168 0         0 $$out .= $sec->{$key} . $DELIMITER;
169 0         0 return 1;
170             }
171             }
172             elsif ( $$chunk =~ s/^\Q$key\E// ) {
173 68         224 $$out .= $sec->{$key} . $DELIMITER;
174 68         568 return 1;
175             }
176             }
177              
178 21         142 return;
179             }
180              
181             sub locate {
182 1     1 0 4 my $path = (caller)[0];
183 1         3 my $file = $_[0];
184              
185 1         5 $path =~ s|::|/|g;
186 1         3 $path =~ s|\w+\$||;
187              
188 1 50       23 unless ( -e $file ) {
189 1         3 foreach my $inc (@INC) {
190 2 50       80 last if -e ( $file = join( '/', $inc, $_[0] ) );
191 2 100       71 last if -e ( $file = join( '/', $inc, $path, $_[0] ) );
192             }
193             }
194              
195 1 50       34 return -e $file ? $file : undef;
196             }
197              
198             sub geek_decode {
199 1     1 0 610 my $code = shift;
200 1         5 my $obj = __PACKAGE__->new(@_); # XXX should auto-detect version
201              
202 1         6 return $obj->decode($code);
203             }
204              
205             sub geek_encode {
206 0     0 0   my $code = shift;
207 0           my $obj = __PACKAGE__->new(@_); # XXX should auto-detect version
208              
209 0           return $obj->encode($code);
210             }
211              
212             1;
213              
214             __END__