File Coverage

blib/lib/App/news.pm
Criterion Covered Total %
statement 63 75 84.0
branch 17 22 77.2
condition 5 9 55.5
subroutine 5 5 100.0
pod 0 4 0.0
total 90 115 78.2


line stmt bran cond sub pod time code
1             # Copyright (C) 2023 Alex Schroeder <alex@gnu.org>
2             #
3             # This program is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation; either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see <http://www.gnu.org/licenses/>.
15              
16             package App::news;
17              
18 6     6   228457 use Modern::Perl '2018';
  6         43  
  6         235  
19              
20             require Exporter;
21             our @ISA = qw(Exporter);
22             our @EXPORT_OK = qw(wrap html_unwrap ranges sranges);
23              
24             our $VERSION = 1.07;
25              
26             =head1 NAME
27              
28             App::news - a web front-end for a news server
29              
30             =head1 DESCRIPTION
31              
32             This is a collection of functions for F<script/news>, which see.
33              
34             use App::news qw(wrap);
35             $body = wrap($body);
36              
37             B<wrap> does text wrapping appropriate for plain text message bodies as used in
38             mail and news articles.
39              
40             If a line is shorter than 50 characters, it is not wrapped.
41              
42             Lines are wrapped to be 72 characters or shorter.
43              
44             Quotes are handled as long as only ">" is used for quoting.
45              
46             B<ranges> translates a list of message numbers into an array of numbers or
47             arrays suitable for XOVER.
48              
49             B<sranges> translates the output of I<ranges> into a string for humans to read,
50             i.e. "1-2,4".
51              
52             =head1 AUTHOR
53              
54             Alex Schroeder
55              
56             =head1 LICENSE
57              
58             GNU Affero General Public License
59              
60             =cut
61              
62             sub wrap {
63 4     4 0 143 my @lines = split(/\n/, shift);
64 4         8 my @result;
65 4         6 my $min = 50;
66 4         5 my $max = 72;
67 4         6 my $buffer;
68 4         6 my $prefix = '';
69 4         9 for (@lines) {
70 14         50 my ($new_prefix) = /^([> ]*)/;
71 14   66     124 my $no_wrap = (/^$prefix\s*$/ or length() < $min);
72             # end old paragraph with the old prefix if the prefix changed or a short line
73             # came up
74 14 50 33     37 if ($buffer and ($new_prefix ne $prefix or $no_wrap)) {
      66        
75 1         8 push(@result, $prefix . $buffer);
76 1         4 $buffer = '';
77             }
78             # set new prefix
79 14         18 $prefix = $new_prefix;
80             # print short lines without stripping trailing whitespace
81 14 100       25 if ($no_wrap) {
82 11         15 push(@result, $_);
83 11         23 next;
84             }
85             # continue old paragraph
86 3 50       7 $buffer .= " " if $buffer;
87             # strip the prefix
88 3         11 $buffer .= substr($_, length($prefix));
89             # wrap what we have
90 3         9 while (length($buffer) > $max) {
91             # if there's a word boundary at $max, break before
92 2 50       25 if (substr($buffer, 0, $max - length($prefix) + 1) =~ /(\s+(\S+))\S$/) {
93 2         10 push(@result, $prefix . substr($buffer, 0, $max - length($prefix) - length($1)));
94 2         10 $buffer = substr($buffer, $max - length($prefix) - length($2));
95             } else {
96 0         0 my $line = substr($buffer, 0, $max - length($prefix));
97 0         0 $line =~ s/\s+$//;
98 0         0 push(@result, $prefix . $line);
99 0         0 $buffer = substr($buffer, $max - length($prefix));
100 0         0 $buffer =~ s/^\s+//;
101             }
102             }
103             }
104 4 100       10 push(@result, $prefix . $buffer) if $buffer;
105 4         35 return join("\n", @result) . "\n";
106             }
107              
108             sub html_unwrap {
109 2     2 0 13 my @lines = split(/\n/, shift);
110 2         7 my $result;
111 2         6 my $depth = 0;
112 2         11 for (@lines) {
113 6         21 chomp;
114 6         31 my ($prefix) = /^([> ]*)/;
115 6         19 my $new_depth = () = $prefix =~ />/g;
116 6         24 s/^([> ]*)//;
117 6         15 my $closed = 0;
118 6         22 while ($new_depth < $depth) {
119 0         0 $result .= "</blockquote>";
120 0         0 $depth--;
121 0         0 $closed = 1;
122             }
123 6 50       24 $result .= "\n" unless $closed; # closing blockquote already added a break
124 6         21 while ($depth < $new_depth) {
125 0         0 $result .= '<blockquote>';
126 0         0 $depth++;
127             }
128 6         16 s/&/&amp;/g;
129 6         15 s/</&lt;/g;
130 6         16 s/>/&gt;/g;
131 6         16 $result .= $_;
132             }
133 2         14 while ($depth > 0) {
134 0         0 $result .= "</blockquote>";
135 0         0 $depth--;
136             }
137 2         31 return $result;
138             }
139              
140             sub ranges {
141 9 50   9 0 137 return [] unless @_;
142 9         16 my $last = shift;
143 9         16 my $curr = $last;
144 9         16 my $ranges = [];
145 9         20 for my $n (@_) {
146 17 100       41 if ($n == $curr + 1) {
    100          
147 11         18 $curr = $n;
148             } elsif ($last == $curr) {
149 3         6 push(@$ranges, $last);
150 3         6 $last = $curr = $n;
151             } else {
152 3         8 push(@$ranges, [$last, $curr]);
153 3         5 $last = $curr = $n;
154             }
155             }
156 9 100       22 if ($curr > $last) {
157 5         13 push(@$ranges, [$last, $curr]);
158             } else {
159 4         10 push(@$ranges, $curr);
160             }
161 9         26 return $ranges;
162             }
163              
164             sub sranges {
165 8     8 0 13 my $ranges = shift;
166 8 100       15 join(",", map { ref ? join("-", @$_) : $_ } @$ranges);
  13         72  
167             }
168              
169             1;