File Coverage

blib/lib/News/Newsrc.pm
Criterion Covered Total %
statement 237 237 100.0
branch 115 124 92.7
condition 9 13 69.2
subroutine 40 40 100.0
pod 29 34 85.2
total 430 448 95.9


line stmt bran cond sub pod time code
1             package News::Newsrc;
2              
3 3     3   3148 use 5;
  3         10  
  3         385  
4 3     3   16 use strict;
  3         5  
  3         92  
5 3     3   4178 use Set::IntSpan;
  3         62154  
  3         31756  
6              
7             $News::Newsrc::VERSION = '1.11';
8             $Set::IntSpan::Empty_String = '';
9              
10              
11             sub new
12             {
13 25     25 1 1073 my ($class, $file, %options) = @_;
14              
15 25         90 my $newsrc = { group => {},
16             list => [] };
17              
18 25   33     140 bless $newsrc, ref $class || $class;
19              
20 25 100 100     61 $newsrc->load($file) or $options{create} or die "Can't load $file: $!\n" if $file;
      100        
21              
22 24         73 return $newsrc;
23             }
24              
25              
26             sub load
27             {
28 15     15 1 792 my($newsrc, $file) = @_;
29              
30 15 100       33 $file or $file = "$ENV{HOME}/.newsrc";
31 15         35 $newsrc->{file } = $file;
32 15         21 $newsrc->{group} = { };
33 15         30 $newsrc->{list } = [ ];
34              
35 15 100       549 open(NEWSRC, $file) or return '';
36 12         232 my $lines = [ ]; # whole file
37 12         114 close(NEWSRC);
38              
39 12         17 eval { $newsrc->import_rc($lines) };
  12         28  
40 12 100       34 $@ and die "News::Newsrc::load: file $file: $@";
41              
42 10         32 1
43             }
44              
45              
46             sub _scan # Initializes a Newsrc object from a string. Used for testing.
47             {
48 2     2   10 my($newsrc, $lines) = @_;
49              
50 2         6 my @lines = split /\n/, $lines;
51 2         6 $newsrc->import_rc(@lines);
52             }
53              
54              
55             sub import_rc
56             {
57 16     16 1 31 my $newsrc = shift;
58 16 100       41 my $lines = ref $_[0] ? $_[0] : [ @_ ];
59              
60 16         29 $newsrc->{group} = { };
61 16         30 $newsrc->{list } = [ ];
62              
63 16         26 my $line_number = 1;
64 16         28 for my $line (@$lines)
65             {
66 46         48 eval { $newsrc->parse($line) };
  46         90  
67 46 100       459 $@ and die "News::Newsrc::import_rc: line $line_number: $@";
68              
69 44         73 $line_number++;
70             }
71             }
72              
73              
74             sub parse # parses a single line from a newsrc file
75             {
76 46     46 0 63 my($newsrc, $line) = @_;
77              
78 46 100       144 $line =~ /\S/ or return;
79 44         146 $line =~ s/\s//g;
80              
81 44 100       185 $line =~ /^ ([^!:]+) ([!:]) (.*) $/x or
82             die "News::Newsrc::parse: Bad newsrc line: $line";
83              
84 43         118 my($name, $mark, $articles) = ($1, $2, $3);
85              
86 43 100       122 valid Set::IntSpan $articles or
87             die "News::Newsrc::parse: Bad article list: $line";
88              
89 42         2979 my $group = { name => $name,
90             subscribed => $mark eq ':',
91             articles => Set::IntSpan->new($articles) };
92              
93 42         2382 $newsrc->{group}{$name} = $group;
94 42         45 push(@{$newsrc->{list}}, $group);
  42         103  
95             }
96              
97              
98             sub save
99             {
100 6     6 1 257 my $newsrc = shift;
101              
102 6 100       21 $newsrc->{file} or $newsrc->{file} = "$ENV{HOME}/.newsrc";
103 6         16 $newsrc->save_as($newsrc->{file});
104             }
105              
106              
107             sub save_as
108             {
109 7     7 1 76 my($newsrc, $file) = @_;
110              
111 7 100 50     153 -e $file and
112             (rename($file, "$file.bak") or
113             die "News::Newsrc::save_as: Can't rename $file, $file.bak: $!\n");
114              
115 7 50       387 open(NEWSRC, "> $file") or
116             die "News::Newsrc::save_as: Can't open $file: $!\n";
117              
118 7         17 $newsrc->{file} = $file;
119 7         20 eval { $newsrc->format($file) };
  7         14  
120 7         92 close NEWSRC;
121 7 50       26 die $@ if $@;
122             }
123              
124              
125             sub format
126             {
127 7     7 1 10 my($newsrc, $file) = @_;
128              
129 7         9 for my $group (@{$newsrc->{list}})
  7         23  
130             {
131 1         3 my $name = $group->{name};
132 1 50       3 my $sub = $group->{subscribed} ? ':' : '!';
133 1         5 my $articles = $group->{articles}->run_list;
134 1 50       29 my $space = $articles ? ' ' : '';
135 1 50       19 print NEWSRC "$name$sub$space$articles\n" or
136             die "News::Newsrc::format: Can't write $file: $!\n";
137             }
138             }
139              
140              
141             sub export_rc
142             {
143 2     2 1 15 my $newsrc = shift;
144              
145 6         6 my @lines = map { my $group = $_;
  2         8  
146 6         11 my $name = $group->{name};
147 6 100       10 my $sub = $group->{subscribed} ? ':' : '!';
148 6         16 my $articles = $group->{articles}->run_list;
149 6 50       148 my $space = $articles ? ' ' : '';
150 2         4 "$name$sub$space$articles\n" } @{$newsrc->{list}};
  6         19  
151              
152 2 100       12 wantarray ? @lines : \@lines
153             }
154              
155              
156             sub _dump # Formats a Newsrc object to a string. Used for testing
157             {
158 55     55   315 my $newsrc = shift;
159              
160 55         58 my $dump = '';
161 55         51 for my $group (@{$newsrc->{list}})
  55         106  
162             {
163 267         320 my $name = $group->{name};
164 267 100       439 my $sub = $group->{subscribed} ? ':' : '!';
165 267         623 my $articles = $group->{articles}->run_list;
166 267 100       3329 $articles = ' ' . $articles if $articles =~ /^\d/;
167 267         530 $dump .= "$name$sub$articles\n";
168             }
169              
170             $dump
171 55         122 }
172              
173              
174             sub add_group
175             {
176 59     59 1 974 my($newsrc, $name, %options) = @_;
177              
178 59 100       157 if ($newsrc->{group}{$name})
179             {
180 2 100       11 $options{replace} or return 0;
181 1         4 $newsrc->del_group($name);
182             }
183              
184 58         158 my $group = { name => $name,
185             subscribed => 1,
186             articles => Set::IntSpan->new };
187              
188 58         1208 $newsrc->{group}{$name} = $group;
189 58         136 $newsrc->_insert($group, %options);
190              
191 58         179 1
192             }
193              
194              
195             sub move_group
196             {
197 13     13 1 753 my($newsrc, $name, %options) = @_;
198 13         25 my $group = $newsrc->{group}{$name};
199 13 50       25 $group or return 0;
200              
201 13         15 $newsrc->{list} = [ grep { $_->{name} ne $name } @{$newsrc->{list}} ];
  91         173  
  13         27  
202 13         41 $newsrc->_insert($group, %options);
203 13         86 1
204             }
205              
206              
207             sub Splice(\@$$@)
208             {
209 6     6 0 10 my($array, $offset, $length, @list) = @_;
210              
211 6 100       13 $offset > @$array and $offset = @$array;
212 6 100       11 $offset < -@$array and $offset = -@$array;
213 6         52 splice @$array, $offset, $length, @list;
214             }
215              
216              
217             sub _insert
218             {
219 71     71   109 my($newsrc, $group, %options) = @_;
220              
221 71         92 my $list = $newsrc->{list};
222              
223 71         86 my($where, $arg) = ('', '');
224 71 100       144 $options{where} and $where = $options{where};
225 71 100       134 ref $where and ($where, $arg) = @$where;
226              
227 71         97 for ($where)
228             {
229 71 100       143 /first/ and unshift @$list, $group;
230 71 100       124 /last/ and push @$list, $group;
231 71 100       196 /^$/ and push @$list, $group; # default
232 71 100       126 /alpha/ and Alpha ($list, $group);
233 71 100       172 /before/ and Before ($list, $group, $arg);
234 71 100       124 /after/ and After ($list, $group, $arg);
235 71 100       810 /number/ and Splice @$list, $arg, 0, $group;
236             }
237             }
238              
239              
240             sub Alpha
241             {
242 4     4 0 5 my($list, $group, $before) = @_;
243 4         6 my $name = $group->{name};
244              
245 4         10 for my $i (0..$#$list)
246             {
247 13 100       30 if ($name lt $list->[$i]{name})
248             {
249 3         4 splice @$list, $i, 0, $group;
250 3         6 return;
251             }
252             }
253              
254 1         3 push @$list, $group;
255             }
256              
257              
258             sub Before
259             {
260 4     4 0 7 my($list, $group, $before) = @_;
261 4         6 my $name = $group->{name};
262              
263 4         10 for my $i (0..$#$list)
264             {
265 27 100       58 if ($list->[$i]{name} eq $before)
266             {
267 2         4 splice @$list, $i, 0, $group;
268 2         4 return;
269             }
270             }
271              
272 2         5 push @$list, $group;
273             }
274              
275              
276             sub After
277             {
278 4     4 0 6 my($list, $group, $after) = @_;
279 4         7 my $name = $group->{name};
280              
281 4         22 for my $i (0..$#$list)
282             {
283 27 100       55 if ($list->[$i]{name} eq $after)
284             {
285 2         4 splice @$list, $i+1, 0, $group;
286 2         5 return;
287             }
288             }
289              
290 2         5 push @$list, $group;
291             }
292              
293              
294             sub del_group
295             {
296 3     3 1 101 my($newsrc, $name) = @_;
297              
298 3 100       15 $newsrc->{group}{$name} or return 0;
299              
300 2         6 delete $newsrc->{group}{$name};
301 2         3 $newsrc->{list} = [ grep { $_->{name} ne $name } @{$newsrc->{list}} ];
  6         16  
  2         5  
302              
303 2         14 1
304             }
305              
306              
307             sub subscribe
308             {
309 3     3 1 103 my($newsrc, $name, %options) = @_;
310 3 100       13 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
311 3         16 $newsrc->{group}{$name}{subscribed} = 1;
312             }
313              
314              
315             sub unsubscribe
316             {
317 3     3 1 95 my($newsrc, $name, %options) = @_;
318 3 100       12 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
319 3         17 $newsrc->{group}{$name}{subscribed} = 0;
320             }
321              
322              
323             sub mark
324             {
325 206     206 1 4757 my($newsrc, $name, $article, %options) = @_;
326 206 100       460 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
327 206         550 $newsrc->{group}{$name}{articles}->insert($article);
328             }
329              
330              
331             sub mark_list
332             {
333 4     4 1 166 my($newsrc, $name, $list, %options) = @_;
334 4 100       15 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
335 4         8 my $group = $newsrc->{group}{$name};
336 4         6 my $articles = union { $group->{articles} } $list;
  4         14  
337 4         459 $group->{articles} = $articles;
338             }
339              
340              
341             sub mark_range
342             {
343 5     5 1 107 my($newsrc, $name, $from, $to, %options) = @_;
344 5 100       46 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
345 5         10 my $group = $newsrc->{group}{$name};
346 5         42 my $range = new Set::IntSpan "$from-$to";
347 5         317 my $articles = union { $group->{articles} } $range;
  5         20  
348 5         226 $group->{articles} = $articles;
349             }
350              
351              
352             sub unmark
353             {
354 3     3 1 90 my($newsrc, $name, $article, %options) = @_;
355 3 100       12 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
356 3         12 $newsrc->{group}{$name}{articles}->remove($article);
357             }
358              
359              
360             sub unmark_list
361             {
362 3     3 1 124 my($newsrc, $name, $list, %options) = @_;
363 3 100       13 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
364 3         6 my $group = $newsrc->{group}{$name};
365 3         4 my $articles = diff { $group->{articles} } $list;
  3         12  
366 3         291 $group->{articles} = $articles;
367             }
368              
369              
370             sub unmark_range
371             {
372 3     3 1 108 my($newsrc, $name, $from, $to, %options) = @_;
373 3 100       13 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
374 3         7 my $group = $newsrc->{group}{$name};
375 3         13 my $range = new Set::IntSpan "$from-$to";
376 3         135 my $articles = diff { $group->{articles} } $range;
  3         9  
377 3         128 $group->{articles} = $articles;
378             }
379              
380              
381             sub exists
382             {
383 12     12 1 50 my($newsrc, $name) = @_;
384 12 100       55 $newsrc->{group}{$name} ? 1 : ''
385             }
386              
387              
388             sub subscribed
389             {
390 3     3 1 35 my($newsrc, $name) = @_;
391 3 100       9 $newsrc->exists($name) and $newsrc->{group}{$name}{subscribed}
392             }
393              
394              
395             sub marked
396             {
397 5     5 1 112 my($newsrc, $name, $article) = @_;
398              
399             $newsrc->exists($name) and
400 5 100       8 member { $newsrc->{group}{$name}{articles} } $article
  4         20  
401             }
402              
403              
404             sub num_groups
405             {
406 1     1 1 4 my $newsrc = shift;
407 1         3 my $list = $newsrc->{list};
408 1         3 scalar @$list
409             }
410              
411              
412             sub groups
413             {
414 3     3 1 121 my $newsrc = shift;
415 3         6 my $list = $newsrc->{list};
416 3         6 my @list = map { $_->{name} } @$list;
  23         47  
417 3 100       25 wantarray ? @list : \@list;
418             }
419              
420              
421             sub sub_groups
422             {
423 2     2 1 101 my $newsrc = shift;
424 2         5 my $list = $newsrc->{list};
425 2         5 my @list = map { $_->{name} } grep { $_->{'subscribed'} } @$list;
  6         13  
  10         17  
426 2 100       17 wantarray ? @list : \@list;
427             }
428              
429              
430             sub unsub_groups
431             {
432 2     2 1 87 my $newsrc = shift;
433 2         3 my $list = $newsrc->{list};
434 2         4 my @list = map { $_->{name} } grep { not $_->{'subscribed'} } @$list;
  4         9  
  10         15  
435 2 100       14 wantarray ? @list : \@list;
436             }
437              
438              
439             sub marked_articles
440             {
441 5     5 1 161 my($newsrc, $name, %options) = @_;
442 5 100       22 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
443 5         6 my @marked = elements { $newsrc->{group}{$name}{articles} };
  5         20  
444 5 100       79 wantarray ? @marked : \@marked
445             }
446              
447              
448             sub unmarked_articles
449             {
450 5     5 1 187 my($newsrc, $name, $from, $to, %options) = @_;
451 5 100       18 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
452 5         18 my $range = new Set::IntSpan "$from-$to";
453 5         256 my $unmarked = diff $range $newsrc->{group}{$name}{articles};
454 5         214 my @unmarked = elements $unmarked;
455 5 100       99 wantarray ? @unmarked : \@unmarked
456             }
457              
458             sub get_articles
459             {
460 18     18 1 1425 my($newsrc, $name, %options) = @_;
461 18 100       56 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
462 18         59 $newsrc->{group}{$name}{articles}->run_list;
463             }
464              
465              
466             sub set_articles
467             {
468 11     11 1 287 my($newsrc, $name, $articles, %options) = @_;
469              
470 11 100       31 valid Set::IntSpan $articles or return 0;
471 6         341 my $set = new Set::IntSpan $articles;
472 6 50       250 finite $set or return 0;
473 6         43 my $min = $set->min;
474 6 50 66     75 defined $min and $min < 0 and return 0;
475              
476 6 100       24 $newsrc->{group}{$name} or $newsrc->add_group($name, %options);
477 6         11 $newsrc->{group}{$name}{articles} = $set;
478 6         22 1
479             }
480              
481             1
482              
483             __END__