File Coverage

blib/lib/Math/OEIS/SortedFile.pm
Criterion Covered Total %
statement 54 73 73.9
branch 13 26 50.0
condition 5 8 62.5
subroutine 16 20 80.0
pod 0 8 0.0
total 88 135 65.1


line stmt bran cond sub pod time code
1             # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020 Kevin Ryde
2              
3             # This file is part of Math-OEIS.
4             #
5             # Math-OEIS is free software; you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-OEIS is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-OEIS. If not, see .
17              
18              
19             package Math::OEIS::SortedFile;
20 4     4   75079 use 5.006;
  4         35  
21 4     4   19 use strict;
  4         7  
  4         111  
22 4     4   24 use warnings;
  4         7  
  4         124  
23 4     4   20 use Carp 'croak';
  4         8  
  4         206  
24 4     4   2021 use Search::Dict;
  4         4443  
  4         212  
25 4     4   1577 use Math::OEIS;
  4         10  
  4         274  
26              
27 4     4   30 eval q{use Scalar::Util 'weaken'; 1}
  4         9  
  4         138  
28             || eval q{sub weaken { $_[0] = undef }; 1 }
29             || die "Oops, error making a weaken() fallback: $@";
30              
31             our $VERSION = 15;
32              
33             # singleton here results in a separate instance object in each derived subclass
34 4     4   1956 use Class::Singleton;
  4         1206  
  4         3092  
35             our @ISA = ('Class::Singleton');
36             #
37             # Called by ->instance() from Class::Singleton. Must dispatch back up the
38             # heirarchy to get a derived $class->new() rather than just \&new here.
39             sub _new_instance {
40 3     3   2650 my $class = shift;
41 3         18 return $class->new(@_);
42             }
43              
44             # uncomment this to run the ### lines
45             # use Smart::Comments;
46              
47              
48             # Keep track of all instances which exist and on an ithread CLONE re-open
49             # filehandles in the instances so they have their own independent file
50             # positions in the new thread.
51             my %instances;
52             sub DESTROY {
53 5     5   2628 my ($self) = @_;
54 5         81 delete $instances{$self+0};
55             }
56             sub CLONE {
57             # my ($class) = @_;
58 0     0   0 foreach my $self (values %instances) {
59 0         0 $self->close;
60             }
61             }
62              
63             sub new {
64 5     5 0 1842 my $class = shift;
65 5         19 my $self = bless { @_ }, $class;
66 5         41 weaken($instances{$self+0} = $self);
67 5         17 return $self;
68             }
69              
70             sub default_filename {
71 0     0 0 0 my ($self) = @_;
72 0         0 return Math::OEIS->local_filename($self->base_filename);
73             }
74              
75             sub filename {
76 2     2 0 7 my ($self) = @_;
77 2 50 33     18 if (ref $self && defined $self->{'filename'}) {
78 2         11 return $self->{'filename'};
79             }
80 0         0 return $self->default_filename;
81             }
82             sub filename_or_empty {
83 0     0 0 0 my ($self) = @_;
84 0         0 my $filename = $self->filename;
85 0 0       0 if (defined $filename) {
86 0         0 return $filename;
87             }
88 0         0 return '';
89             }
90              
91             sub fh {
92 13     13 0 23 my ($self) = @_;
93 13 50       31 if (! ref $self) { $self = $self->instance; }
  0         0  
94 13 100       31 if (! exists $self->{'fh'}) {
95 2 50       17 if (defined (my $filename = $self->filename)) {
96 2         4 my $fh;
97 2 50       96 if (open $fh, '<', $filename) {
98 2         16 $self->{'fh'} = $fh;
99             }
100             }
101             }
102 13         93 return $self->{'fh'};
103             }
104             sub close {
105 0     0 0 0 my ($self) = @_;
106 0 0       0 if (my $fh = delete $self->{'fh'}) {
107 0 0       0 if (! close $fh) {
108 0         0 my $err = "$!";
109 0         0 croak "Cannot close ",$self->filename_or_empty,": ",$err;
110             }
111             }
112             }
113              
114             # $line is a line from the names or stripped file.
115             # Return the A-number string from the line such as "A000001",
116             # or empty string if unrecognised or a comment line etc.
117             sub line_to_anum {
118 283     283 0 1183 my ($self, $line) = @_;
119             ### line_to_anum(): $line
120 283 100       946 return ($line =~ /^(A\d{6,})/ ? $1 : '');
121             }
122              
123             # $anum is an A-number string like "A000001".
124             # Return that number's line from the names or stripped file.
125             # If no such line then return undef.
126             # If a read error then croak.
127             sub anum_to_line {
128 13     13 0 29 my ($self, $anum) = @_;
129             ### $anum
130 13 50       93 if (! ref $self) { $self = $self->instance; }
  0         0  
131 13   50     39 my $fh = $self->fh || return undef;
132             my $pos = Search::Dict::look ($fh, $anum,
133             { xfrm => sub {
134 268     268   3866 my ($line) = @_;
135             ### $line
136 268 50       433 my ($got_anum) = $self->line_to_anum($line)
137             or return '';
138             ### $got_anum
139 268         516 return $got_anum;
140 13         110 } });
141 13 50       393 if ($pos < 0) {
142 0         0 my $err = "$!";
143 0         0 croak 'Error reading ',$self->filename_or_empty,': ',$err;
144             }
145              
146             # Ensure the line is in fact the $anum requested, since a bad $anum causes
147             # Search::Dict::look() to return the file position before where it would
148             # be found if it were present. This may be at end-of-file too, or perhaps
149             # even the file could be empty somehow.
150             #
151 13         23 my $line;
152 13 100 100     147 return (defined ($line = readline $fh)
153             && $self->line_to_anum($line) eq $anum
154             ? $line
155             : undef);
156             }
157              
158             1;
159             __END__