Apply 79_archiver_slash.patch
[mspang/vmailman.git] / bin / cleanarch
1 #! @PYTHON@
2
3 # Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 # USA.
19
20 """Clean up an .mbox archive file.
21
22 The archiver looks for Unix-From lines separating messages in an mbox archive
23 file.  For compatibility, it specifically looks for lines that start with
24 "From " -- i.e. the letters capital-F, lowercase-r, o, m, space, ignoring
25 everything else on the line.
26
27 Normally, any lines that start "From " in the body of a message should be
28 escaped such that a > character is actually the first on a line.  It is
29 possible though that body lines are not actually escaped.  This script
30 attempts to fix these by doing a stricter test of the Unix-From lines.  Any
31 lines that start "From " but do not pass this stricter test are escaped with a
32 > character.
33
34 Usage: cleanarch [options] < inputfile > outputfile
35 Options:
36     -s n
37     --status=n
38         Print a # character every n lines processed
39
40     -q / --quiet
41         Don't print changed line information to standard error.
42
43     -n / --dry-run
44         Don't actually output anything.
45
46     -h / --help
47         Print this message and exit
48 """
49
50 import re
51 import sys
52 import getopt
53 import mailbox
54
55 import paths
56 from Mailman.i18n import _
57
58 cre = re.compile(mailbox.UnixMailbox._fromlinepattern)
59
60 # From RFC 2822, a header field name must contain only characters from 33-126
61 # inclusive, excluding colon.  I.e. from oct 41 to oct 176 less oct 072.  Must
62 # use re.match() so that it's anchored at the beginning of the line.
63 fre = re.compile(r'[\041-\071\073-\176]+')
64
65
66 \f
67 def usage(code, msg=''):
68     if code:
69         fd = sys.stderr
70     else:
71         fd = sys.stdout
72     print >> fd, _(__doc__)
73     if msg:
74         print >> fd, msg
75     sys.exit(code)
76
77
78 \f
79 def escape_line(line, lineno, quiet, output):
80     if output:
81         sys.stdout.write('>' + line)
82     if not quiet:
83         print >> sys.stderr, _('Unix-From line changed: %(lineno)d')
84         print >> sys.stderr, line[:-1]
85
86
87 \f
88 def main():
89     try:
90         opts, args = getopt.getopt(
91             sys.argv[1:], 'hqns:',
92             ['help', 'quiet', 'dry-run', 'status='])
93     except getopt.error, msg:
94         usage(1, msg)
95
96     quiet = False
97     output = True
98     status = -1
99
100     for opt, arg in opts:
101         if opt in ('-h', '--help'):
102             usage(0)
103         elif opt in ('-q', '--quiet'):
104             quiet = True
105         elif opt in ('-n', '--dry-run'):
106             output = False
107         elif opt in ('-s', '--status'):
108             try:
109                 status = int(arg)
110             except ValueError:
111                 usage(1, _('Bad status number: %(arg)s'))
112
113     if args:
114         usage(1)
115
116     lineno = 0
117     statuscnt = 0
118     messages = 0
119     prevline = None
120     while True:
121         lineno += 1
122         line = sys.stdin.readline()
123         if not line:
124             break
125         if line.startswith('From '):
126             if cre.match(line):
127                 # This is a real Unix-From line.  But it could be a message
128                 # /about/ Unix-From lines, so as a second order test, make
129                 # sure there's at least one RFC 2822 header following
130                 nextline = sys.stdin.readline()
131                 lineno += 1
132                 if not nextline:
133                     # It was the last line of the mbox, so it couldn't have
134                     # been a Unix-From
135                     escape_line(line, lineno, quiet, output)
136                     break
137                 fieldname = nextline.split(':', 1)
138                 if len(fieldname) < 2 or not fre.match(nextline):
139                     # The following line was not a header, so this wasn't a
140                     # valid Unix-From
141                     escape_line(line, lineno, quiet, output)
142                     if output:
143                         sys.stdout.write(nextline)
144                 else:
145                     # It's a valid Unix-From line
146                     messages += 1
147                     if output:
148                         # Before we spit out the From_ line, make sure the
149                         # previous line was blank.
150                         if prevline is not None and prevline <> '\n':
151                             sys.stdout.write('\n')
152                         sys.stdout.write(line)
153                         sys.stdout.write(nextline)
154             else:
155                 # This is a bogus Unix-From line
156                 escape_line(line, lineno, quiet, output)
157         elif output:
158             # Any old line
159             sys.stdout.write(line)
160         if status > 0 and (lineno % status) == 0:
161             sys.stderr.write('#')
162             statuscnt += 1
163             if statuscnt > 50:
164                 print >> sys.stderr
165                 statuscnt = 0
166         prevline = line
167     print >> sys.stderr, _('%(messages)d messages found')
168
169
170 \f
171 if __name__ == '__main__':
172     main()