File Coverage

lib/File/URIList.pm
Criterion Covered Total %
statement 26 129 20.1
branch 0 78 0.0
condition 0 23 0.0
subroutine 9 18 50.0
pod 7 7 100.0
total 42 255 16.4


line stmt bran cond sub pod time code
1             # Copyright (c) 2025 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: module for reading and writing RFC 2483 URI lists
6              
7             package File::URIList;
8              
9 1     1   385512 use v5.20;
  1         3  
10 1     1   4 use strict;
  1         1  
  1         19  
11 1     1   7 use warnings;
  1         1  
  1         49  
12              
13 1     1   4 use Fcntl qw(SEEK_SET);
  1         1  
  1         64  
14 1     1   550 use URI;
  1         4418  
  1         26  
15 1     1   391 use URI::file;
  1         5105  
  1         29  
16 1     1   6 use Carp;
  1         1  
  1         56  
17 1     1   5 use parent qw(Data::Identifier::Interface::Userdata Data::Identifier::Interface::Subobjects);
  1         1  
  1         4  
18              
19             use constant {
20 1         1197 CRLF => "\015\012",
21 1     1   2125 };
  1         1  
22              
23             our $VERSION = v0.04;
24              
25             my %_check_defaults = (
26             blank_lines => 'die',
27             extra_spaces => 'die',
28             no_scheme => 'die',
29             slash_as_local => undef,
30             );
31              
32              
33              
34             #@returns __PACKAGE__
35             sub new {
36 0     0 1   my ($pkg, $handle, %opts) = @_;
37 0           my %check = %_check_defaults;
38 0           my $self = bless {check => \%check}, $pkg;
39              
40 0           foreach my $key (keys %_check_defaults) {
41 0 0         if (exists $opts{$key}) {
42 0           $check{$key} = delete $opts{$key};
43             }
44             }
45              
46 0 0         if (defined(my $baseuri = delete $opts{baseuri})) {
47 0 0         $baseuri = URI->new($baseuri) unless eval { $baseuri->isa('URI') };
  0            
48 0           $self->{baseuri} = $baseuri;
49             }
50              
51 0 0         croak 'Stray options passed' if scalar keys %opts;
52              
53 0 0         unless (ref $handle) {
54 0 0         open(my $fh, '<', $handle) or die $!;
55 0           $handle = $fh;
56             }
57              
58 0 0         binmode($handle) or die $!;
59              
60 0           $self->{fh} = $handle;
61              
62 0           return $self;
63             }
64              
65              
66             sub write_comment {
67 0     0 1   my ($self, @list) = @_;
68 0           my $fh = $self->{fh};
69              
70 0           foreach my $ent (@list) {
71 0 0         if (ref($ent) eq 'ARRAY') {
    0          
72 0           $self->write_comment(@{$ent});
  0            
73             } elsif (ref($ent)) {
74 0           print $fh '# ';
75 0           $self->write_list($ent);
76             } else {
77 0           foreach my $line (split/\015?\012/, $ent) {
78 0           print $fh '# ', $line, CRLF;
79             }
80             }
81             }
82             }
83              
84              
85             sub write_list {
86 0     0 1   my ($self, @list) = @_;
87 0           my $fh = $self->{fh};
88              
89 0           foreach my $ent (@list) {
90 0 0         if (ref($ent) eq 'ARRAY') {
    0          
91 0           $self->write_list(@{$ent});
  0            
92             } elsif (ref($ent)) {
93 0 0 0       if ($ent->isa('URI')) {
    0          
    0          
    0          
    0          
    0          
94 0           print $fh $ent->as_string, CRLF;
95             } elsif ($ent->isa('Data::Identifier')) {
96 0           print $fh $ent->uri, CRLF;
97             } elsif ($ent->isa('Data::URIID::Result')) {
98 0           print $fh $ent->url, CRLF;
99             } elsif ($ent->isa('Data::URIID::Base') || $ent->isa('Data::Identifier::Interface::Simple')) {
100 0           print $fh $ent->as('uri'), CRLF;
101             } elsif ($ent->isa('Data::Identifier::Cloudlet')) {
102 0           $self->write_list($ent->roots);
103             } elsif ($ent->isa(__PACKAGE__)) {
104             $ent->read_to(sub {
105 0     0     print $fh $_[1]->as_string, CRLF;
106 0           });
107             } else {
108 0           croak 'Unsupported object passed';
109             }
110             } else {
111 0           print $fh URI->new($ent)->as_string, CRLF;
112             }
113             }
114             }
115              
116              
117             sub read_to {
118 0     0 1   my ($self, $cb, %opts) = @_;
119 0           my $fh = $self->{fh};
120 0   0       my $as = delete($opts{as}) // 'URI';
121              
122 0 0         croak 'Stray options passed' if scalar keys %opts;
123              
124             # Preload modules as needed:
125 0 0         if ($as ne 'URI') {
126 0           require Data::Identifier;
127             }
128              
129 0 0         if (ref($cb) eq 'ARRAY') {
130 0           my $list = $cb;
131 0     0     $cb = sub { push(@{$list}, $_[1]) };
  0            
  0            
132             }
133              
134 0           while (defined(my $line = <$fh>)) {
135 0           $line =~ s/\015?\012$//;
136              
137 0 0         next if $line =~ /^#/;
138              
139 0 0 0       if ($line =~ /^\s/ || $line =~ /\s$/) {
140 0   0       my $action = $self->{check}{extra_spaces} // '';
141              
142 0 0         if ($action eq 'die') {
    0          
    0          
143 0           croak 'Input line with extra spaces, aborting';
144             } elsif ($action eq 'trim') {
145 0           $line =~ s/^\s+//;
146 0           $line =~ s/\s+$//;
147             } elsif ($action eq 'pass') {
148             # no-op
149             } else {
150 0           croak 'Input line with extra spaces and bad handling action selected: '.$action;
151             }
152             }
153              
154 0 0         if ($line eq '') {
155 0   0       my $action = $self->{check}{blank_lines} // '';
156              
157 0 0         if ($action eq 'die') {
    0          
    0          
    0          
158 0           croak 'Blank line in input, aborting';
159             } elsif ($action eq 'skip') {
160 0           next;
161             } elsif ($action eq 'undef') {
162 0           $cb->($self, undef);
163 0           next;
164             } elsif ($action eq 'pass') {
165             # no-op
166             } else {
167 0           croak 'Blank line in input and bad handling action selected: '.$action;
168             }
169             }
170              
171 0 0 0       if ($line =~ /^\// && $self->{check}{slash_as_local}) {
172 0           $line = URI::file->new($line);
173             }
174              
175 0 0 0       if (!ref($line) && !defined($self->{baseuri}) && $line !~ /^[a-zA-Z][a-zA-Z0-9\+\.\-]+:/) {
      0        
176 0   0       my $action = $self->{check}{no_scheme} // '';
177              
178 0 0         if ($action eq 'die') {
    0          
179 0           croak 'URI with no scheme, aborting';
180             } elsif ($action eq 'pass') {
181             # no-op
182             } else {
183 0           croak 'URI with no scheme and bad handling action selected: '.$action;
184             }
185             }
186              
187 0 0         unless (ref $line) {
188 0 0         if (defined $self->{baseuri}) {
189 0           $line = URI->new_abs($line, $self->{baseuri});
190             } else {
191 0           $line = URI->new($line);
192             }
193             }
194              
195 0 0         if ($as ne 'URI') {
196 0           $line = $line->Data::Identifier::as($as);
197             }
198              
199 0           $cb->($self, $line);
200             }
201             }
202              
203              
204             sub read_as_list {
205 0     0 1   my ($self, %opts) = @_;
206 0           my $list = [];
207 0           my $listas = delete $opts{listas};
208              
209 0           delete $opts{list}; # we are always in list mode
210              
211 0           $self->read_to($list, %opts);
212              
213 0 0         if (defined($listas)) {
214 0           require Data::Identifier::Cloudlet;
215 0           $list = Data::Identifier::Cloudlet->new(root => $list)->as($listas);
216             }
217              
218 0           return $list;
219             }
220              
221              
222             sub rewind {
223 0     0 1   my ($self) = @_;
224 0           my $fh = $self->{fh};
225              
226 0 0         $fh->seek(0, SEEK_SET) or die $!;
227 0           $fh->input_line_number(1);
228             }
229              
230              
231             sub clear {
232 0     0 1   my ($self) = @_;
233 0           my $fh = $self->{fh};
234              
235 0           $self->rewind;
236              
237 0           $fh->truncate(0);
238             }
239              
240             1;
241              
242             __END__