File Coverage

blib/lib/Text/Flowed.pm
Criterion Covered Total %
statement 43 57 75.4
branch 8 16 50.0
condition 15 33 45.4
subroutine 10 11 90.9
pod 3 3 100.0
total 79 120 65.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # vim:ts=4:sw=4
3              
4             package Text::Flowed;
5              
6             $VERSION = '0.14';
7              
8             require Exporter;
9             @ISA = qw(Exporter);
10             @EXPORT = qw();
11             @EXPORT_OK = qw(reformat quote quote_fixed);
12              
13 2     2   139106 use strict;
  2         6  
  2         66  
14 2     2   10 use vars qw($MAX_LENGTH $OPT_LENGTH);
  2         4  
  2         1892  
15              
16             # MAX_LENGTH: This is the maximum length that a line is allowed to be
17             # (unless faced with a word that is unreasonably long). This module will
18             # re-wrap a line if it exceeds this length.
19             $MAX_LENGTH = 79;
20              
21             # OPT_LENGTH: When this module wraps a line, the newly created lines
22             # will be split at this length.
23             $OPT_LENGTH = 72;
24              
25             # reformat($text, [\%args])
26             # Reformats $text, where $text is format=flowed plain text as described
27             # in RFC 2646.
28             #
29             # $args->{quote}: Add a level of quoting to the beginning of each line.
30             # $args->{fixed}: Interpret unquoted/all lines as format=fixed.
31             # $args->{max_length}: The maximum length of any line.
32             # $args->{opt_length}: The maximum length of wrapped lines.
33             sub reformat {
34 3     3 1 18 my @input = split("\n", $_[0]);
35 3         5 my $args = $_[1];
36 3   33     17 $args->{max_length} ||= $MAX_LENGTH;
37 3   33     13 $args->{opt_length} ||= $OPT_LENGTH;
38 3         4 my @output = ();
39              
40             # Process message line by line
41 3         8 while (@input) {
42             # Count and strip quote levels
43 3         8 my $line = shift(@input);
44 3         6 my $num_quotes = _num_quotes($line);
45 3         7 $line = _unquote($line);
46              
47             # Should we interpret this line as flowed?
48 3 50 33     20 if (!$args->{fixed} ||
      66        
49             ($args->{fixed} == 1 && $num_quotes)) {
50 3         9 $line = _unstuff($line);
51             # While line is flowed, and there is a next line, and the
52             # next line has the same quote depth
53 3   66     9 while (_flowed($line) && @input &&
      66        
54             _num_quotes($input[0]) == $num_quotes) {
55             # Join the next line
56 1         4 $line .= _unstuff(_unquote(shift(@input)));
57             }
58             }
59             # Ensure line is fixed, since we joined all flowed lines
60 3         9 $line = _trim($line);
61              
62             # Increment quote depth if we're quoting
63 3 100       10 $num_quotes++ if $args->{quote};
64              
65 3 50       13 if (!$line) {
    50          
66             # Line is empty
67 0         0 push(@output, '>' x $num_quotes);
68             } elsif (length($line) + $num_quotes <= $args->{max_length} - 1) {
69             # Line does not require rewrapping
70 3         11 push(@output, '>' x $num_quotes . _stuff($line, $num_quotes));
71             } else {
72             # Rewrap this paragraph
73 0         0 while ($line) {
74             # Stuff and re-quote the line
75 0         0 $line = '>' x $num_quotes . _stuff($line, $num_quotes);
76              
77             # Set variables used in regexps
78 0         0 my $min = $num_quotes + 1;
79 0         0 my $opt1 = $args->{opt_length} - 1;
80 0         0 my $max1 = $args->{max_length} - 1;
81 0 0 0     0 if (length($line) <= $args->{opt_length}) {
    0 0        
82             # Remaining section of line is short enough
83 0         0 push(@output, $line);
84 0         0 last;
85             } elsif ($line =~ /^(.{$min,$opt1}) (.*)/ ||
86             $line =~ /^(.{$min,$max1}) (.*)/ ||
87             $line =~ /^(.{$min,})? (.*)/) {
88             # 1. Try to find a string as long as opt_length.
89             # 2. Try to find a string as long as max_length.
90             # 3. Take the first word.
91 0         0 push(@output, "$1 ");
92 0         0 $line = $2;
93             } else {
94             # One excessively long word left on line
95 0         0 push(@output, $line);
96 0         0 last;
97             }
98             }
99             }
100             }
101              
102 3         23 return join("\n", @output)."\n";
103             }
104              
105             # quote()
106             # A convenience wrapper for reformat(, {quote => 1}).
107             sub quote {
108 0     0 1 0 return reformat($_[0], {quote => 1});
109             }
110              
111             # quote_fixed()
112             # A convenience wrapper for reformat(, {quote => 1, fixed => 1}).
113             sub quote_fixed {
114 1     1 1 7 return reformat($_[0], {quote => 1, fixed => 1});
115             }
116              
117             # _num_quotes()
118             # Returns the number of leading '>' characters in .
119             sub _num_quotes {
120 8     8   31 $_[0] =~ /^(>*)/;
121 8         38 return length($1);
122             }
123              
124             # _unquote()
125             # Removes all leading '>' characters from .
126             sub _unquote {
127 8     8   14 my $line = shift;
128 8         21 $line =~ s/^(>+)//g;
129 8         23 return $line;
130             }
131              
132             # _flowed()
133             # Returns 1 if is flowed; 0 otherwise.
134             sub _flowed {
135 6     6   7 my $line = shift;
136             # Lines with only spaces in them are not considered flowed
137             # (heuristic to recover from sloppy user input)
138 6 50       25 return 0 if $line =~ /^ *$/;
139 6         62 return $line =~ / $/;
140             }
141              
142             # _trim()
143             # Removes all trailing ' ' characters from .
144             sub _trim {
145 5     5   9 $_ = shift;
146 5         13 $_ =~ s/ +$//g;
147 5         24 return $_;
148             }
149              
150             # _stuff(, )
151             # Space-stuffs if it starts with " " or ">" or "From ", or if
152             # quote depth is non-zero (for aesthetic reasons so that there is a
153             # space after the ">").
154             sub _stuff {
155 6     6   12 my ($text, $num_quotes) = @_;
156 6 100 33     67 if ($text =~ /^ / || $text =~ /^>/ || $text =~ /^From / ||
      66        
      100        
157             $num_quotes > 0) {
158 3         15 return " $text";
159             }
160 3         14 return $text;
161             }
162              
163             # _unstuff()
164             # If starts with a space, remove it.
165             sub _unstuff {
166 6     6   9 $_ = shift;
167 6         20 $_ =~ s/^ //;
168 6         16 return $_;
169             }
170              
171             1;
172              
173             __END__