Index: mailer.py =================================================================== --- mailer.py (revision 38207) +++ mailer.py (working copy) @@ -140,7 +140,7 @@ subject = subject[:(truncate_subject - 3)] + "..." return subject - def start(self, group, params): + def start(self, group, params, paths): """Override this method. Begin writing an output representation. GROUP is the name of the configuration file group which is causing this output to be produced. @@ -181,7 +181,7 @@ def __init__(self, cfg, repos, prefix_param): OutputBase.__init__(self, cfg, repos, prefix_param) - def start(self, group, params): + def start(self, group, params, paths): # whitespace (or another character) separated list of addresses # which must be split into a clean list to_addr_in = self.cfg.get('to_addr', group, params) @@ -208,7 +208,111 @@ and self.reply_to[2] == ']': self.reply_to = self.reply_to[3:] - def mail_headers(self, group, params): + def mail_headers(self, group, params, paths): + h_path = self.cfg.get('headers_path', group, params) + if len(h_path) >= 3 and h_path[0] == '[' \ + and h_path[2] == ']': + headers_path= \ + [_f for _f in h_path[3:].split(h_path[1]) if _f] + else: + headers_path = [_f for _f in h_path.split() if _f] + try: + h_path = headers_path[0].split('/') + except IndexError: + pass + h_length = len(h_path) + header_list = [] + for p in paths: + plist = p.split('/') + # the path is usually in 3 different forms, 1, 2 and 3. The 2 forms 1 and 2 + # are variants of each other, either the path starts with trunk, or trunk is + # the 2. field. Lets see some examples. + # 1: path = module/trunk/submod/... + # 2: path = trunk/module/submod/... + # Besides trunk there are 2 more special words: branches and tags. Examples: + # 1: path = module/branches/branchname/submod/... + # 2: path = branches/branchname/module/submod/... + # 1: path = module/tags/tagname/submod/... + # 2: path = tags/tagname/module/submod/... + # the 3. different form of the path is that neither field1 or field2 is a + # special word. Example: + # 3: path = module/submod/... + # because of this we treat plist[0-2] specially. + if 'trunk' == plist[0]: + extra_header = 'X-branch: trunk' + header_list.append(extra_header) + plist = plist[1:] + try: + if 'trunk' == plist[1]: + extra_header = 'X-branch: trunk' + header_list.append(extra_header) + plist = [plist[0]] + plist[2:] + except IndexError: + pass + if 'branches' == plist[0]: + extra_header = 'X-branch: ' + plist[1] + header_list.append(extra_header) + plist = plist[2:] + try: + if 'branches' == plist[1]: + extra_header = 'X-branch: ' + plist[2] + header_list.append(extra_header) + plist = [plist[0]] + plist[3:] + except IndexError: + pass + if 'tags' == plist[0]: + extra_header = 'X-tag: ' + plist[1] + header_list.append(extra_header) + plist = plist[2:] + try: + if 'tags' == plist[1]: + extra_header = 'X-tag: ' + plist[2] + header_list.append(extra_header) + plist = [plist[0]] + plist[3:] + except IndexError: + pass + # we are now through the special words and will process the adjusted path + for x in range(h_length): + # traverse from start to first h_path[x] == ... + if '_' == h_path[x]: + continue + if '...' == h_path[x]: + break + try: + name = h_path[x] + except IndexError: + name = '' + try: + value = plist[x] + except IndexError: + value = '' + if '' != name and '' != value: + extra_header = 'X-' + '%s: %s' % (name, value) + header_list.append(extra_header) + i=-1 + while i>=-h_length: + # traverse from end to first h_path[i] == ... + if '_' == h_path[i]: + i=i-1 + continue + if '...' == h_path[i]: + i=i-1 + break + try: + name = h_path[i] + except IndexError: + name = '' + try: + value = plist[i] + except IndexError: + value = '' + if '' != name and '' != value: + extra_header = 'X-' + '%s: %s' % (name, value) + header_list.append(extra_header) + i=i-1 + # because we process all paths, and because these paths are very likely + # to contain identical segments we will make a set out of it. + header_list = set(header_list) subject = self.make_subject(group, params) try: subject.encode('ascii') @@ -228,6 +332,8 @@ % (self.from_addr, ', '.join(self.to_addrs), subject, group, self.repos.author or 'no_author', self.repos.rev, os.path.basename(self.repos.repos_dir)) + for y in header_list: + hdrs = '%s%s\n' % (hdrs, y) if self.reply_to: hdrs = '%sReply-To: %s\n' % (hdrs, self.reply_to) return hdrs + '\n' @@ -236,13 +342,13 @@ class SMTPOutput(MailedOutput): "Deliver a mail message to an MTA using SMTP." - def start(self, group, params): - MailedOutput.start(self, group, params) + def start(self, group, params, paths): + MailedOutput.start(self, group, params, paths) self.buffer = StringIO() self.write = self.buffer.write - self.write(self.mail_headers(group, params)) + self.write(self.mail_headers(group, params, paths)) def finish(self): server = smtplib.SMTP(self.cfg.general.smtp_hostname) @@ -260,7 +366,7 @@ OutputBase.__init__(self, cfg, repos, prefix_param) self.write = sys.stdout.write - def start(self, group, params): + def start(self, group, params, paths): self.write("Group: " + (group or "defaults") + "\n") self.write("Subject: " + self.make_subject(group, params) + "\n\n") @@ -277,8 +383,8 @@ # figure out the command for delivery self.cmd = cfg.general.mail_command.split() - def start(self, group, params): - MailedOutput.start(self, group, params) + def start(self, group, params, paths): + MailedOutput.start(self, group, params, paths) ### gotta fix this. this is pretty specific to sendmail and qmail's ### mailwrapper program. should be able to use option param substitution @@ -290,7 +396,7 @@ self.write = self.pipe.stdin.write # start writing out the mail message - self.write(self.mail_headers(group, params)) + self.write(self.mail_headers(group, params, paths)) def finish(self): # signal that we're done sending content @@ -381,7 +487,7 @@ renderer = TextCommitRenderer(self.output) for (group, param_tuple), (params, paths) in self.groups.items(): - self.output.start(group, params) + self.output.start(group, params, paths) # generate the content for this group and set of params generate_content(renderer, self.cfg, self.repos, self.changelist, Index: mailer.conf.example =================================================================== --- mailer.conf.example (revision 38207) +++ mailer.conf.example (working copy) @@ -243,6 +243,82 @@ # Set to 0 to turn off. #truncate_subject = 200 +# headers_path = +# +# The headers_path consists of one or more fields seperated by a / just +# like a real path. Each of these fields will be added to the header with +# a 'X-' infront and a ': $value' where $value will be the contents of the +# real path field in the same position. +# +# Before processing the headers_path the real path is checked for 3 +# special words: 'trunk', 'branches' and 'tags' +# +# The special word trunk is detected in real path field 0 or 1, and will +# always generate an extra header that looks like: 'X-branch: trunk' +# The special word branches is also detected in real path field 0 or 1, +# and will always generate an extra header that looks like: +# 'X-branch: $branchname' where $branchname is the real_path field right +# after the special word 'branches'. +# Tags are also detected in the same way as branches, but will generate an +# extra header looking like this: 'X-tag: $tagname' +# +# If these special words are detected the special words are removed from +# the real_path and the real_path is processed as if the special words +# were not there to begin with. In the case of branches and tags, the +# extra branchname or tagname field is also removed from the path. +# +# The headers_path is processed first from left to right, and then from +# right to left. There are 2 special values possible for a field in the +# headers_path, '_' and '...' +# _ means 'real_path value is not importent, generate no header' +# ... means 'no more importent values, stop processing in this direction' +# +# real_path = trunk/tools/hook-scripts/mailer/mailer.py +# headers_path = module/.../file +# this means that the emails would include these extra headers: +# X-branch: trunk +# X-module: tools +# X-file: mailer.py +# +# headers_path = module/.../directory/file +# real_path = trunk/tools/hook-scripts/mailer/mailer.py +# X-branch: trunk +# X-module: tools +# X-directory: mailer +# X-file: mailer.py +# +# headers_path = ... (same as empty headers_path) +# headers_path = branch/.../foobar/.../file +# foobar will not be an email header +# +# the single char _ means a single pathsgment, aka what is between the 2 / +# but it is not used for anything in the headers. It is entirely possible +# to have one or more _ like a/_/_/b +# headers_path = _/module/.../directory/file +# real_path = trunk/tools/hook-scripts/mailer/mailer.py +# X-branch: trunk +# X-module: hook-scripts +# X-directory: mailer +# X-file: mailer.py +# +# it is also perfectly legal to have a headers_path longer than the +# real path. If this happens, some fields will just have no value, +# and thus not included in the email +# +# overlapping pathsegments are also legal +# headers_path = module/submod/directory/subdir/.../foo/bar/file +# real_path = trunk/tools/hook-scripts/mailer/mailer.py +# X-branch: trunk +# X-module: tools +# X-submod: hook-scripts +# X-directory: mailer +# X-subdir: mailer.py +# X-foo: hook-scripts +# X-bar: mailer +# X-file: mailer.py +# +headers_path = module/submod/.../directory/subdir/file + # -------------------------------------------------------------------------- [maps]