]>
code.delx.au - gnu-emacs-elpa/blob - admin/forward-diffs.py
2 ### forward-diffs.py --- forward emacs-elpa-diffs mails to maintainers
4 ## Copyright (C) 2012 Free Software Foundation, Inc.
6 ## Author: Glenn Morris <rgm@gnu.org>
8 ## This program is free software; you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation, either version 3 of the License, or
11 ## (at your option) any later version.
13 ## This program is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
23 ## Forward emails from the emacs-elpa-diffs mailing list to the
24 ## maintainer(s) of the modified files.
26 ## Two modes of operation:
28 ## 1) Create the maintfile (really this is just an optimization):
29 ## forward-diffs.py --create -p packagesdir -m maintfile
31 ## 2) Call from eg procmail to forward diffs. Example usage:
34 ## * ^TO_emacs-elpa-diffs@gnu\.org
35 ## | forward-diffs.py -p packagedir -m maintfile -l logfile -s sender
39 ## packagedir = /path/to/packages
40 ## sender = your email address
41 ## logfile = file to write log to (you might want to rotate/compress/examine it)
42 ## maintfile = file listing packages and their maintainers, with format:
45 ## package2 email2,email3
47 ## Use "nomail" for the email field to not send a mail.
49 ## overmaintfile = like maintfile, but takes precedence over it.
62 ## Scan FILE for Author or Maintainer (preferred) headers.
63 ## Return a list of all email addresses found in MAINTS.
64 def scan_file(file, maints
):
68 except Exception as err
:
69 lfile
.write('Error opening file %s: %s\n' % (file, str(err
)))
72 ## Max number of lines to scan looking for a maintainer.
73 ## (20 seems to be the highest at present).
83 if ( nline
> max_lines
): break
85 ## Try and de-obfuscate. Worth it?
86 line
= re
.sub( '(?i) AT ', '@', line
)
87 line
= re
.sub( '(?i) DOT ', '.', line
)
89 if cont
: # continued header?
90 reg
= re
.match( ('%s[ \t]+[^:]*?<?([\w.-]+@[\w.-]+)>?' % prefix
), line
, re
.I
)
91 if not reg
: # not a continued header
94 if ( type == "maint" ): break
97 ## Check for one header immediately after another.
99 reg
= re
.match( '([^ ]+)? *(Author|Maintainer)s?: .*?<?([\w.-]+@[\w.-]+)>?', line
, re
.I
)
109 prefix
= reg
.group(1) or ""
112 type = "maint" if re
.search( 'Maintainer', type, re
.I
) else "auth"
113 ## maints = [] does the wrong thing.
114 if type == "maint": del maints
[:]
120 ## Scan all the files under dir for maintainer information.
121 ## Write to stdout, or optional argument outfile (which is overwritten).
122 def scan_dir(dir, outfile
=None):
124 dir = re
.sub( '/+$', '', dir) + '/' # ensure trailing /
126 if not os
.path
.isdir(dir):
127 sys
.stderr
.write('No such directory: %s\n' % dir)
133 fd
= open( outfile
, 'w' )
134 except Exception as err
:
135 sys
.stderr
.write("Error opening `%s': %s\n" % (outfile
, str(err
)))
139 for dirpath
, dirnames
, filenames
in os
.walk(dir):
140 for file in filenames
:
141 path
= os
.path
.join(dirpath
, file)
143 scan_file(path
, maints
)
144 ## This would skip printing empty maints.
145 ## That would mean we would scan the file each time for no reason.
146 ## if not maints: continue
147 path
= re
.sub( '^%s' % dir, '', path
)
148 string
= "%-50s %s\n" % (path
, ",".join(maints
))
157 usage
="""usage: %prog <-p /path/to/packages> <-m maintfile>
158 <-l logfile -s sender|--create> [-o overmaintfile] [--sendmail] [--debug]
159 Take a GNU ELPA diff on stdin, and forward it to the maintainer(s)."""
161 parser
= optparse
.OptionParser()
162 parser
.set_usage ( usage
)
163 parser
.add_option( "-m", dest
="maintfile", default
=None,
164 help="file listing packages and maintainers")
165 parser
.add_option( "-l", dest
="logfile", default
=None,
166 help="file to append output to")
167 parser
.add_option( "-o", dest
="overmaintfile", default
=None,
168 help="override file listing packages and maintainers")
169 parser
.add_option( "-p", dest
="packagedir", default
=None,
170 help="path to packages directory")
171 parser
.add_option( "-s", dest
="sender", default
=None,
172 help="sender address for forwards")
173 parser
.add_option( "--create", dest
="create", default
=False,
174 action
="store_true", help="create maintfile")
175 parser
.add_option( "--sendmail", dest
="sendmail", default
=False,
176 action
="store_true", help="use sendmail rather than smtp")
177 parser
.add_option( "--debug", dest
="debug", default
=False,
178 action
="store_true", help="debug only, do not send mail")
181 ( opts
, args
) = parser
.parse_args()
184 if not opts
.maintfile
:
185 parser
.error('No maintfile specified')
187 if not opts
.packagedir
:
188 parser
.error('No packagedir specified')
190 if not os
.path
.isdir(opts
.packagedir
):
191 sys
.stderr
.write('No such directory: %s\n' % opts
.packagedir
)
197 parser
.error('No logfile specified')
200 parser
.error('No sender specified')
203 ## Create the maintfile.
205 scan_dir( opts
.packagedir
, opts
.maintfile
)
210 lfile
= open( opts
.logfile
, 'a' )
211 except Exception as err
:
212 sys
.stderr
.write('Error opening logfile: %s\n' % str(err
))
217 mfile
= open( opts
.maintfile
, 'r' )
218 except Exception as err
:
219 lfile
.write('Error opening maintfile: %s\n' % str(err
))
222 ## Each element is package: maint1, maint2, ...
226 if re
.match( '#| *$', line
): continue
227 (pack
, maint
) = line
.split()
228 maints
[pack
] = maint
.split(',')
233 if opts
.overmaintfile
:
235 ofile
= open( opts
.overmaintfile
, 'r' )
236 except Exception as err
:
237 lfile
.write('Error opening overmaintfile: %s\n' % str(err
))
241 if re
.match( '#| *$', line
): continue
242 (pack
, maint
) = line
.split()
243 maints
[pack
] = maint
.split(',')
253 resent_via
= 'GNU ELPA diff forwarder'
255 message
= email
.message_from_string( text
)
257 (msg_name
, msg_from
) = email
.utils
.parseaddr( message
['from'] )
259 lfile
.write('\nDate: %s\n' % str(datetime
.datetime
.now()))
260 lfile
.write('Message-ID: %s\n' % message
['message-id'])
261 lfile
.write('From: %s\n' % msg_from
)
263 if resent_via
== message
['x-resent-via']:
264 lfile
.write('Mail loop; aborting\n')
272 for line
in text
.splitlines():
274 if re
.match( 'modified:$', line
):
278 if not start
: continue
280 if re
.match( ' *$', line
): break
283 reg
= re
.match( 'packages/([^/]+)', line
.strip() )
289 lfile
.write('Package: %s\n' % pack
)
291 if pack
in packs_seen
:
292 lfile
.write('Already seen this package\n')
295 packs_seen
.append(pack
)
298 if not pack
in maints
:
299 lfile
.write('Unknown maintainer\n')
303 for maint
in maints
[pack
]:
305 lfile
.write('Maint: %s\n' % maint
)
308 if maint
in maints_seen
:
309 lfile
.write('Already seen this maintainer\n')
312 maints_seen
.append(maint
)
315 if maint
== "nomail":
316 lfile
.write('Not resending, no mail is requested\n')
320 if maint
== msg_from
:
321 lfile
.write('Not resending, since maintainer = committer\n')
326 forward
.add_header('X-Resent-Via', resent_via
)
327 forward
.add_header('Resent-To', maint
)
328 forward
.add_header('Resent-From', opts
.sender
)
330 lfile
.write('Resending via %s...\n' % ('sendmail'
331 if opts
.sendmail
else 'smtp') )
334 if opts
.debug
: continue
338 s
= os
.popen("/usr/sbin/sendmail -i -f %s %s" %
339 (opts
.sender
, maint
), "w")
340 s
.write(forward
.as_string())
343 lfile
.write('Sendmail exit status: %s\n' % status
)
348 s
= smtplib
.SMTP('localhost')
349 except Exception as err
:
350 lfile
.write('Error opening smtp: %s\n' % str(err
))
354 s
.sendmail(opts
.sender
, maint
, forward
.as_string())
355 except Exception as err
:
356 lfile
.write('Error sending smtp: %s\n' % str(err
))
360 ### forward-diffs.py ends here