File Coverage

blib/lib/Games/Go/AGA/Parse/Register.pm
Criterion Covered Total %
statement 143 165 86.6
branch 49 70 70.0
condition 21 25 84.0
subroutine 26 27 96.3
pod 3 12 25.0
total 242 299 80.9


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Games::Go::AGA::Parse::Register.pm
4             #
5             # PODNAME: Games::Go::AGA::Parse::Register
6             # ABSTRACT: models AGA register.tde file information
7             #
8             # AUTHOR: Reid Augustin (REID),
9             # COMPANY: LucidPort Technology, Inc.
10             # CREATED: 11/19/2010 03:13:05 PM PST
11             #===============================================================================
12              
13 1     1   1133 use 5.008;
  1         3  
  1         50  
14 1     1   6 use strict;
  1         1  
  1         52  
15 1     1   8 use warnings;
  1         2  
  1         55  
16              
17             package Games::Go::AGA::Parse::Register;
18 1     1   648 use parent 'Games::Go::AGA::Parse';
  1         403  
  1         9  
19              
20 1     1   54 use Carp;
  1         2  
  1         100  
21 1     1   714 use Readonly;
  1         3742  
  1         81  
22 1     1   551 use Scalar::Util qw( looks_like_number );
  1         2  
  1         95  
23 1     1   897 use String::Tokenizer;
  1         1826  
  1         43  
24 1     1   605 use Games::Go::AGA::Parse::Util qw( is_ID is_Rating is_Rank_or_Rating normalize_ID );
  1         2  
  1         92  
25 1     1   593 use Games::Go::AGA::Parse::Exceptions;
  1         4  
  1         2147  
26              
27             our $VERSION = '0.042'; # VERSION
28              
29             sub last_name {
30 3     3 0 4 my ($self, $new) = @_;
31              
32 3 50       9 if (@_ > 1) {
33 0         0 $self->{last_name} = $new;
34             }
35 3   50     12 return $self->{last_name} || '';
36             }
37              
38             sub first_name {
39 3     3 0 4 my ($self, $new) = @_;
40              
41 3 50       8 if (@_ > 1) {
42 0         0 $self->{first_name} = $new;
43             }
44 3   100     15 return $self->{first_name} || '';
45             }
46              
47             sub id {
48 9     9 0 13 my ($self, $new) = @_;
49              
50 9 50       34 if (@_ > 1) {
51 0         0 $self->{id} = $new;
52             }
53 9   100     48 return $self->{id} || '';
54             }
55              
56             sub rank {
57 6     6 0 9 my ($self, $new) = @_;
58              
59 6 100       15 if (@_ > 1) {
60 3         7 $self->{rank} = $new;
61 3 100       9 $self->{rank} += 0 if (is_Rating($new)); # numify
62             }
63 6   50     20 return $self->{rank} || '';
64             }
65              
66             sub flags {
67 3     3 0 5 my ($self, $new) = @_;
68              
69 3 50       9 if (@_ > 1) {
70 0         0 $self->{flags} = $new;
71             }
72 3   100     14 return $self->{flags} || [];
73             }
74              
75             sub club {
76 3     3 0 4 my ($self, $new) = @_;
77              
78 3 50       8 if (@_ > 1) {
79 0         0 $self->{club} = $new;
80             }
81 3   100     14 return $self->{club} || '';
82             }
83              
84             sub comment {
85 5     5 0 8 my ($self, $new) = @_;
86              
87 5 50       12 if (@_ > 1) {
88 0         0 $self->{comment} = $new;
89             }
90 5   100     37 return $self->{comment} || '';
91             }
92              
93             sub directive {
94 4     4 0 7 my ($self, $new) = @_;
95              
96 4 50       8 if (@_ > 1) {
97 0         0 $self->{directive} = $new;
98             }
99 4   100     19 return $self->{directive} || '';
100             }
101              
102             sub value {
103 1     1 0 2 my ($self, $new) = @_;
104              
105 1 50       5 if (@_ > 1) {
106 0         0 $self->{value} = $new;
107             }
108 1 50       7 return defined $self->{value} ? $self->{value} : '';
109             }
110              
111             sub as_array {
112 0     0 1 0 my ($self, $new) = @_;
113              
114 0         0 my @ret;
115 0 0       0 if ($self->id) {
    0          
116 0         0 @ret = map
117 0         0 { $self->$_ }
118             # fields, in order
119             qw(
120             id
121             last_name
122             first_name
123             rank
124             flags
125             club
126             comment
127             );
128             }
129             elsif ($self->directive) {
130 0         0 @ret = ($self->directive, $self->value);
131             }
132             else {
133 0         0 @ret = ($self->comment);
134             }
135 0 0       0 return wantarray ? @ret : \@ret;
136             }
137              
138             sub as_hash {
139 6     6 1 9 my ($self, $new) = @_;
140              
141 6         7 my %ret;
142 6 100       13 if ($self->id) {
    100          
143 21         52 %ret = map
144 3         4 { $_, $self->$_ }
145             # fields, in order
146             qw(
147             id
148             last_name
149             first_name
150             rank
151             flags
152             club
153             comment
154             );
155             }
156             elsif ($self->directive) {
157 1         3 %ret = (
158             directive => $self->directive,
159             value => $self->value,
160             );
161             }
162             else {
163 2         10 %ret = (
164             comment => $self->comment,
165             );
166             }
167 6 50       91 return wantarray ? %ret : \%ret;
168             }
169              
170              
171             Readonly my $ID => 0;
172             Readonly my $LAST_NAME => 1;
173             Readonly my $FIRST_NAME => 2;
174             Readonly my $FLAGS => 3;
175              
176             Readonly my %name_of_state => (
177             $ID => 'ID',
178             $LAST_NAME => 'LAST_NAME',
179             $FIRST_NAME => 'FIRST_NAME',
180             $FLAGS => 'FLAGS',
181             );
182              
183             Readonly my %state_functions => (
184             $ID => \&_get_id,
185             $LAST_NAME => \&_get_last_name,
186             $FIRST_NAME => \&_get_first_name,
187             $FLAGS => \&_get_flags,
188             );
189              
190             *parse = \&parse_line; # alias parse to parse_line
191             sub parse_line {
192 8     8 1 6978 my ($self, $string) = @_;
193              
194             # initialize
195 8         23 $self->{source} = $string;
196 8         20 map { delete $self->{$_} } qw(
  80         119  
197             id
198             last_name
199             first_name
200             rank
201             flags
202             club
203             comment
204             directive
205             value
206             comment
207             ); # empty arrays
208              
209 8 50       23 return $self->as_hash if (not $string);
210              
211 8         51 my $tokenizer = String::Tokenizer->new(
212             $string, # source string
213             #"~!@#\$\%^&*()`={}[]:;\"'<>,?/|\\\n", # delimiters (doesn't include +-.)
214             "#,=\n",
215             String::Tokenizer->RETAIN_WHITESPACE,
216             );
217 8         1458 my $iter = $tokenizer->iterator;
218              
219 8         365 my $state = $ID; # assume we will see ID first
220              
221             TOKEN:
222 8         49 while ($iter->hasNextToken) {
223 47         337 my $token = $iter->nextToken;
224             # warn "state $name_of_state{$state}: token=<$token>\n";
225              
226 47 100 66     637 if ($token eq "\n") { # a carriage return
    100          
    100          
    50          
227 1         3 last TOKEN;
228             }
229             elsif ($token !~ m/\S/ or # only whitespace
230             $token eq '') { # empty
231 16         44 next TOKEN;
232             }
233             elsif ($token eq '#') { # comment
234 6         10 my @remainder;
235 6         17 push (@remainder, $iter->nextToken) while $iter->hasNextToken;
236 6         397 $token = join('', @remainder);
237 6         18 $state = $self->_get_comment($token);
238 6         41 last TOKEN;
239             }
240             elsif (exists $state_functions{$state}) {
241 24         196 $state = $state_functions{$state}($self, $token);
242 23         171 next TOKEN;
243             }
244             else {
245 0         0 $self->_parse_error(
246             error => "Unknown state: $state",
247             source => $token,
248             );
249             }
250             }
251 7 100       22 if (exists $self->{id}) {
252 4 50       15 $self->{last_name} = join(' ', @{$self->{last_name}} ) if exists $self->{last_name};
  4         10  
253 4 100       16 $self->{first_name} = join(' ', @{$self->{first_name}}) if exists $self->{first_name};
  3         10  
254 12         29 my @missing = grep
255 4         8 { not $self->{$_} }
256             ( qw( id last_name rank ) );
257 4 100       56 if (@missing) {
258 1         12 $self->_parse_error(
259             error => "missing: @missing",
260             source => $string,
261             );
262             }
263              
264             # transfer dropN from comment to flags
265 3 100       11 if (exists $self->{comment}) {
266 2         11 while ($self->{comment} =~ s/\b(drop\d+)\s*//) {
267 0         0 push @{$self->{flags}}, $1;
  0         0  
268             }
269             }
270             }
271 6         16 return $self->as_hash;
272             }
273              
274             sub _get_id {
275 5     5   31 my ($self, $token) = @_;
276              
277 5         19 my $id = normalize_ID($token);
278 5 50       16 if (not is_ID($id)) {
279 0         0 $self->_parse_error(
280             error => "<$token> is not a valid AGA ID",
281             source => $self->{source},
282             );
283             }
284 5         15 $self->{id} = $id;
285 5         16 return $LAST_NAME; # next state
286             }
287              
288             sub _get_last_name {
289 10     10   55 my ($self, $token) = @_;
290              
291 10 100       24 if ($token eq ',') {
292 4 100       12 if (exists $self->{last_name}) {
293 3         10 return $FIRST_NAME;
294             } else {
295 1         30 $self->_parse_error(
296             error => 'missing ID or last name before comma',
297             source => $self->{source},
298             );
299             }
300             }
301 6 100       26 if (is_Rank_or_Rating($token)) {
302 1         5 $self->rank(uc $token);
303 1         4 return $FLAGS; # either flag or trailing comments is next
304             }
305 5         9 push(@{$self->{last_name}}, $token);
  5         16  
306 5         17 return $LAST_NAME;
307             }
308              
309             sub _get_first_name {
310 6     6   31 my ($self, $token) = @_;
311              
312 6 100       16 if (is_Rank_or_Rating($token)) {
313 2         9 $self->rank(uc $token);
314 2         8 return $FLAGS; # either flag or trailing comments is next
315             }
316 4         9 push(@{$self->{first_name}}, $token);
  4         13  
317 4         14 return $FIRST_NAME;
318             }
319              
320             sub _get_flags {
321 3     3   15 my ($self, $token) = @_;
322              
323 3         5 my $flags_ref = $self->{flags};
324 3 100 66     12 if ($flags_ref and
  2   100     14  
325             @{$flags_ref} and
326             ($flags_ref->[-1] eq '=')) { # last entry was '='
327              
328             # Flags formed like Bar = Foo got turned into
329             # three seperate tokens: 'Bar', '=', and 'Foo'.
330             # Combine them into one array element here, and upper-case the
331             # key (i.e: BAR)
332              
333 1         1 pop @{$flags_ref}; # remove equals sign
  1         2  
334 1         1 my $key = uc(pop(@{$flags_ref})); # remove key and upper-case it
  1         4  
335 1 50       4 if ($key eq 'CLUB') {
336 1         9 $self->{club} = $token; # turn CLUB= flags into the club
337             }
338             else {
339             # concatenate with equal sign and current token
340 0         0 push @{$flags_ref}, "$key=$token";
  0         0  
341             }
342             }
343             else {
344 2         3 push( @{$self->{flags}}, $token );
  2         5  
345             }
346 3         9 return $FLAGS;
347             }
348              
349             # $token is the rest of the line following the #
350             sub _get_comment {
351 6     6   11 my ($self, $token) = @_;
352              
353 6 100       22 if ($token =~ m/\A#\s*(\w+)\s*(.*)/) {
354             # lines starting with ## are directives
355 1         3 $self->{directive} = $1;
356 1         4 $self->{value} = $2;
357 1         9 $self->{value} =~ s/\s*$//; # trim trailing whitespace
358             }
359             else {
360 5         14 $self->{comment} = $token;
361 5         38 $self->{comment} =~ s/\s*$//; # trim trailing whitespace
362             }
363 6         21 return $ID; # comments go to end of line, wrap back to start
364             }
365              
366             1;
367              
368             __END__