File Coverage

blib/lib/Chess/PGN/Extract/Stream.pm
Criterion Covered Total %
statement 24 96 25.0
branch 0 32 0.0
condition 0 3 0.0
subroutine 8 17 47.0
pod 4 4 100.0
total 36 152 23.6


line stmt bran cond sub pod time code
1             package Chess::PGN::Extract::Stream;
2 2     2   16944 use 5.008001;
  2         6  
  2         75  
3 2     2   9 use strict;
  2         4  
  2         53  
4 2     2   7 use warnings;
  2         2  
  2         68  
5              
6 2     2   10 use base 'Exporter::Tiny';
  2         3  
  2         612  
7             our @EXPORT = qw| pgn_file read_game read_games |;
8              
9 2     2   2488 use Carp qw| croak |;
  2         2  
  2         120  
10 2     2   1354 use File::Temp qw| tempdir tempfile |;
  2         26052  
  2         165  
11 2     2   331 use Chess::PGN::Extract 'read_games' => { -prefix => '_' };
  2         6  
  2         25  
12 2     2   353 use IO::Handle;
  2         3  
  2         1560  
13              
14             sub new {
15 0     0 1   my ( $class, $pgn_file ) = @_;
16              
17 0 0         croak ("'new' requires a PGN file name")
18             unless defined $pgn_file;
19              
20 0           my $self = {};
21 0           $self->{pgn_file} = $pgn_file;
22 0 0         open my $pgn_handle, '<', $pgn_file
23             or croak ("Cannot open PGN file: \"$pgn_file\"");
24 0           $self->{pgn_handle} = $pgn_handle;
25              
26 0           bless $self => $class;
27             }
28              
29 0     0 1   sub pgn_file { $_[0]->{pgn_file} }
30              
31             sub read_game {
32 0     0 1   ( $_[0]->read_games (1) )[0];
33             }
34              
35             sub read_games {
36 0     0 1   my $self = shift;
37 0           my ($limit) = @_;
38              
39 0           my $handle = $self->{pgn_handle};
40 0 0         return if $handle->eof;
41              
42 0 0         unless ( defined $limit ) {
43 0           return _read_all ($handle);
44             }
45              
46             # Force integer
47 0           $limit = int $limit;
48              
49 0 0         if ( $limit < 0 ) { _read_all ($handle) }
  0 0          
50 0           elsif ( $limit == 0 ) { return }
51             else {
52 0           my ( $game, @games );
53 0   0       while ( $limit-- and $game = _get_one_game_string ($handle) ) {
54 0           push @games, $game;
55             }
56 0           return _read_pgn_string ( join '', @games );
57             }
58             }
59              
60             {
61             # Parser contexts:
62             # $start - Before parsing tag sections
63             # $expect_tag - Parsing tag sections has started
64             # $expect_moves - Parsing moves section has started
65             my ( $start, $expect_tag, $expect_moves ) = 0 .. 2;
66              
67             # Regular expressions to identify which section the given $line is
68             my $blank = qr/^[\s\t]*\n$/;
69             my $tag = qr/^[\s\t]*\[[\s\t]*\w+[\s\t]+\".+\"[\s\t]*\][\s\t]*\n$/;
70             my $tag_begin = qr/^[\s\t]*\[/;
71             # my $moves = ...;
72              
73             # _get_one_game_string ($handle) => $pgn_string
74             sub _get_one_game_string {
75 0     0     my $context = $start;
76 0           _parse_lines ( $_[0], $context, [] );
77             }
78              
79             # _parse_lines ($handle, $context, $buffer) => $pgn_string
80             sub _parse_lines {
81 0 0   0     return join '', @{ $_[2] } if $_[0]->eof;
  0            
82              
83 0           my $line = $_[0]->getline;
84              
85             # Ignore blank lines
86 0 0         goto \&_parse_lines if $line =~ $blank;
87              
88 0 0         if ( $_[1] == $start ) {
    0          
    0          
89              
90 0 0         if ( $line =~ $tag_begin ) {
91 0           _complete_tag_line ($_[0], $line);
92 0           push @{ $_[2] }, $line;
  0            
93 0           $_[1] = $expect_tag;
94 0           goto \&_parse_lines;
95             }
96             else {
97 0           croak ("PGN parse error: Move section started without any tags");
98             }
99             }
100             elsif ( $_[1] == $expect_tag ) {
101              
102 0 0         if ( $line =~ $tag_begin ) {
103 0           _complete_tag_line ($_[0], $line);
104 0           push @{ $_[2] }, $line;
  0            
105 0           goto \&_parse_lines;
106             }
107             else {
108 0           push @{ $_[2] }, $line;
  0            
109 0           $_[1] = $expect_moves;
110 0           goto \&_parse_lines;
111             }
112             }
113             elsif ( $_[1] == $expect_moves ) {
114              
115 0 0         if ( $line =~ $tag_begin ) {
116 0           seek $_[0], -length $line, 1; # go back to the head of $line
117 0           return join '', @{ $_[2] };
  0            
118             }
119             else {
120 0           push @{ $_[2] }, $line;
  0            
121 0           goto \&_parse_lines;
122             }
123             }
124             else {
125 0           croak ("PGN parse error: Unknown context");
126             }
127 0           croak ("PGN parse error: Unknown parse error");
128             }
129              
130             # _complete_tag_line ($handle, $partial_tag_line)
131             sub _complete_tag_line {
132 0 0   0     return if $_[1] =~ $tag;
133 0 0         if ( $_[0]->eof ) {
134 0           croak ("PGN parse error: Parse finished inside a tag section");
135             }
136 0           chomp $_[1];
137 0           $_[1] .= $_[0]->getline;
138 0           goto \&_complete_tag_line;
139             }
140             }
141              
142             # _read_all ($handle) => @games
143             sub _read_all {
144 0     0     my $handle = shift;
145 0           my $all = do { local $/; $handle->getline };
  0            
  0            
146 0           _read_pgn_string ($all);
147             }
148              
149             # _read_pgn_string ($pgn_string) => @games
150             sub _read_pgn_string {
151 0     0     my ($pgn_string) = @_;
152              
153 0           my $tmp_dir = tempdir ( $ENV{TMPDIR} . "/chess_pgn_extract_stream_XXXXXXXX",
154             CLEANUP => 1 );
155 0           my ( $tmp_handle, $tmp_file ) = tempfile ( DIR => $tmp_dir );
156 0           $tmp_handle->print ($pgn_string);
157 0           $tmp_handle->close;
158              
159 0           return _read_games ($tmp_file);
160             }
161              
162             1;
163             __END__