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__ |