Skip to content

Commit e5b551f

Browse files
committed
First iteration of email alias generation for wg- and drafts ( issue ietf-tools#713 ).
- Legacy-Id: 5902
1 parent 451043d commit e5b551f

5 files changed

Lines changed: 402 additions & 3 deletions

File tree

ietf/bin/aliasutil.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# -*- Python -*-
4+
#
5+
# $Id: aliasutil.py $
6+
#
7+
# Author: Markus Stenberg <mstenber@cisco.com>
8+
#
9+
"""
10+
11+
Mailing list alias dumping utilities
12+
13+
"""
14+
15+
ad_replacement = {
16+
# 2009 Ad replacements
17+
'chris.newman@sun.com': 'alexey.melnikov@isode.com',
18+
'townsley@cisco.com': 'rdroms@cisco.com',
19+
'jon.peterson@neustar.biz': 'rjsparks@nostrum.com',
20+
'dward@cisco.com': 'adrian@olddog.co.uk',
21+
22+
# 2010 AD replacements -- activate after spring ietf
23+
'lisa@osafoundation.org': 'stpeter@stpeter.im',
24+
'fluffy@cisco.com': 'gonzalo.camarillo@ericsson.com',
25+
'rcallon@juniper.net': 'stbryant@cisco.com',
26+
'pasi.eronen@nokia.com': 'turners@ieca.com',
27+
'magnus.westerlund@ericsson.com': 'ietfdbh@comcast.net',
28+
29+
# 2011 AD replacements -- activate after spring ietf
30+
'alexey.melnikov@isode.com': 'presnick@qualcomm.com',
31+
'tim.polk@nist.gov': 'stephen.farrell@cs.tcd.ie',
32+
'lars.eggert@nokia.com': 'wes@mti-systems.com',
33+
34+
#2013 AD replacements
35+
'housley@vigilsec.com': 'jari.arkko@piuha.net',
36+
'rdroms.ietf@gmail.com': 'ted.lemon@nominum.com',
37+
'rbonica@juniper.net': 'jjaeggli@zynga.com',
38+
'rjsparks@nostrum.com': 'rbarnes@bbn.com',
39+
}
40+
41+
email_replacement = {
42+
'barryleiba@computer.org': 'barryleiba@gmail.com',
43+
'greg.daley@eng.monash.edu.au': 'gdaley@netstarnetworks.com',
44+
'radia.perlman@sun.com': 'radia@alum.mit.edu',
45+
'lisa@osafoundation.org': 'lisa.dusseault@gmail.com',
46+
'lisa.dusseault@messagingarchitects.com': 'lisa.dusseault@gmail.com',
47+
'scott.lawrence@nortel.com': 'scottlawrenc@avaya.com',
48+
'charliep@computer.org': 'charliep@computer.org, charles.perkins@earthlink.net',
49+
'yaronf@checkpoint.com': 'yaronf.ietf@gmail.com',
50+
'mary.barnes@nortel.com': 'mary.ietf.barnes@gmail.com',
51+
'scottlawrenc@avaya.com': 'xmlscott@gmail.com',
52+
'henk@ripe.net': 'henk@uijterwaal.nl',
53+
'jonne.soininen@nsn.com': 'jonne.soininen@renesasmobile.com',
54+
'tom.taylor@rogers.com': 'tom.taylor.stds@gmail.com',
55+
'rahul@juniper.net': 'raggarwa_1@yahoo.com',
56+
'dward@juniper.net': 'dward@cisco.com',
57+
'alan.ford@roke.co.uk': 'alanford@cisco.com',
58+
'rod.walsh@nokia.com': 'roderick.walsh@tut.fi',
59+
'bob.hinden@nokia.com': 'bob.hinden@gmail.com',
60+
'martin.thomson@commscope.com': 'martin.thomson@gmail.com',
61+
'rjs@estacado.net': 'rjsparks@nostrum.com',
62+
'rbarnes@bbn.com': 'rlb@ipv.sx',
63+
}
64+
65+
66+
def rewrite_email_address(email, is_ad):
67+
""" Prettify the email address (and if it's empty, skip it by
68+
returning None). """
69+
if not email:
70+
return
71+
email = email.strip()
72+
if not email:
73+
return
74+
if email[0]=='<' and email[-1] == '>':
75+
email = email[1:-1]
76+
# If it doesn't look like email, skip
77+
if '@' not in email and '?' not in email:
78+
return
79+
if is_ad:
80+
email = ad_replacement.get(email, email)
81+
email = email_replacement.get(email, email)
82+
return email
83+
84+
def rewrite_address_list(l):
85+
""" This utility function makes sure there is exactly one instance
86+
of an address within the result list, and preserves order
87+
(although it may not be relevant to start with) """
88+
h = {}
89+
for address in l:
90+
#address = address.strip()
91+
if h.has_key(address): continue
92+
h[address] = True
93+
yield address
94+
95+
def dump_sublist(alias, f, wg, is_adlist=False):
96+
if f:
97+
l = f(wg)
98+
else:
99+
l = wg
100+
if not l:
101+
return
102+
# Nones in the list should be skipped
103+
l = filter(None, l)
104+
105+
# Make sure emails are sane and eliminate the Nones again for
106+
# non-sane ones
107+
l = [rewrite_email_address(e, is_adlist) for e in l]
108+
l = filter(None, l)
109+
110+
# And we'll eliminate the duplicates too but preserve order
111+
l = list(rewrite_address_list(l))
112+
if not l:
113+
return
114+
try:
115+
print '%s: %s' % (alias, ', '.join(l))
116+
except UnicodeEncodeError:
117+
# If there's unicode in email address, something is badly
118+
# wrong and we just silently punt
119+
# XXX - is there better approach?
120+
print '# Error encoding', alias, repr(l)
121+
return
122+
return l
123+

ietf/bin/generate-draft-aliases

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# -*- Python -*-
4+
#
5+
# $Id: generate-draft-aliases $
6+
#
7+
# Author: Markus Stenberg <markus.stenberg@iki.fi>
8+
#
9+
"""
10+
11+
This code dumps Django model InternetDraft's contents as postfix email
12+
aliases
13+
14+
<no suffix> (same as -authors)
15+
.authors (list of authors)
16+
.chairs (WG chairs)
17+
.notify (notify emails(?))
18+
.ad (sponsoring AD)
19+
.all (all of the above)
20+
21+
TODO:
22+
23+
- results somewhat inconsistent with the results from the old tool;
24+
should examine why (ask me for diff tool if interested in fixing it)
25+
26+
"""
27+
28+
DRAFT_EMAIL_SUFFIX='@tools.ietf.org'
29+
30+
# boilerplate (from various other ietf/bin scripts)
31+
import os, sys
32+
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
33+
sys.path = [ basedir ] + sys.path
34+
35+
from ietf import settings
36+
from django.core import management
37+
management.setup_environ(settings)
38+
39+
from ietf.doc.models import Document
40+
from ietf.group.utils import get_group_chairs_emails
41+
from aliasutil import *
42+
import time
43+
44+
def get_draft_ad_emails(draft):
45+
" Get AD email for the given draft, if any. "
46+
ad = draft.ad
47+
#return [ad and ad.user and ad.user.email]
48+
return [ad and ad.email_address()]
49+
50+
def get_draft_authors_emails(draft):
51+
" Get list of authors for the given draft."
52+
53+
# This feels 'correct'; however, it creates fairly large delta
54+
return [email.email_address() for email in draft.authors.all()]
55+
56+
# This gives fairly small delta compared to current state,
57+
# however, it seems to be wrong (doesn't check for emails being
58+
# active etc).
59+
#return [email.address for email in draft.authors.all()]
60+
61+
def get_draft_notify_emails(draft):
62+
" Get list of email addresses to notify for the given draft."
63+
n = draft.notify
64+
if not n:
65+
return
66+
l = []
67+
draft_email = draft.name + DRAFT_EMAIL_SUFFIX
68+
for e in n.split(','):
69+
# If the draft name itself is listed as notify list element, we
70+
# expand it (to make results better verifiable with the old ones)
71+
e = e.strip()
72+
if e == draft_email:
73+
l.extend(get_draft_authors_emails(draft))
74+
else:
75+
l.append(e)
76+
# Alternative: if we don't want to do expansion, just this would be
77+
# perhaps better (MTA can do expansion too):
78+
# l = n.split(',')
79+
return l
80+
81+
if __name__ == '__main__':
82+
import datetime
83+
import time
84+
85+
# Year ago?
86+
show_since = datetime.datetime.now() - datetime.timedelta(365)
87+
# 10 years ago?
88+
#show_since = datetime.datetime.now() - datetime.timedelta(10 * 365)
89+
90+
modname = 'ietf.generate_draft_aliases'
91+
date = time.strftime("%Y-%m-%d_%H:%M:%S")
92+
print '# Generated by python -m %s at %s' % (modname, date)
93+
94+
drafts = Document.objects.all()
95+
96+
# Drafts with active status
97+
active_drafts = drafts.filter(states__slug='active')
98+
99+
# Drafts that expired within year
100+
inactive_recent_drafts = drafts.exclude(states__slug='active').filter(expires__gte=show_since)
101+
102+
interesting_drafts = active_drafts | inactive_recent_drafts
103+
104+
for draft in interesting_drafts.distinct().iterator():
105+
# Omit RFCs, we care only about drafts
106+
if draft.docalias_set.filter(name__startswith='rfc'):
107+
continue
108+
name = draft.name
109+
done = []
110+
all = []
111+
def handle_sublist(name, f, o, is_ad=False):
112+
r = dump_sublist(name, f, o, is_ad)
113+
if r:
114+
done.append(name)
115+
all.extend(r)
116+
return r
117+
#.authors (/and no suffix) = authors
118+
119+
# First, do no suffix case
120+
# If no authors, don't generate list either
121+
r = dump_sublist(name, get_draft_authors_emails, draft)
122+
if not r:
123+
continue
124+
handle_sublist('%s%s' % (name, '.authors'), get_draft_authors_emails, draft)
125+
wg = draft.group
126+
if wg:
127+
# .chairs = WG chairs
128+
handle_sublist('%s%s' % (name, '.chairs'), get_group_chairs_emails, wg)
129+
130+
# .ad = sponsoring AD
131+
handle_sublist('%s%s' % (name, '.ad'), get_draft_ad_emails, draft, True)
132+
133+
# .notify = notify email list from the Document
134+
handle_sublist('%s%s' % (name, '.notify'), get_draft_notify_emails, draft)
135+
136+
# .all = everything on 'done' (recursive aliases)
137+
#dump_sublist('%s%s' % (name, '.all'), None, done)
138+
# .all = everything on 'all' (expanded aliases)
139+
dump_sublist('%s%s' % (name, '.all'), None, all)
140+
141+

ietf/bin/generate-wg-aliases

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# -*- Python -*-
4+
#
5+
# $Id: generate-wg-aliases $
6+
#
7+
# Author: Markus Stenberg <markus.stenberg@iki.fi>
8+
#
9+
"""
10+
11+
This code dumps Django model IETFWG's contents as two sets of postfix
12+
mail lists: -ads, and -chairs
13+
14+
"""
15+
16+
# boilerplate (from various other ietf/bin scripts)
17+
import os, sys
18+
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
19+
sys.path = [ basedir ] + sys.path
20+
21+
from ietf import settings
22+
from django.core import management
23+
management.setup_environ(settings)
24+
25+
from ietf.group.models import Group
26+
from ietf.group.utils import get_group_ads_emails, get_group_chairs_emails, get_area_ads_emails, get_area_chairs_emails
27+
from ietf.person.models import Email
28+
from aliasutil import *
29+
30+
# from secr/utils/group.py..
31+
ACTIVE_STATES=['active', 'bof', 'proposed']
32+
33+
if __name__ == '__main__':
34+
import datetime
35+
import time
36+
37+
# Year ago?
38+
#show_since = datetime.datetime.now() - datetime.timedelta(365)
39+
40+
# 2 years ago?
41+
#show_since = datetime.datetime.now() - datetime.timedelta(2 * 365)
42+
43+
# 3 years ago?
44+
#show_since = datetime.datetime.now() - datetime.timedelta(3 * 365)
45+
46+
# 5 years ago?
47+
show_since = datetime.datetime.now() - datetime.timedelta(5 * 365)
48+
49+
modname = 'ietf.generate_wg_aliases'
50+
date = time.strftime("%Y-%m-%d_%H:%M:%S")
51+
print '# Generated by python -m %s at %s' % (modname, date)
52+
wgs = Group.objects.filter(type='wg').all()
53+
54+
print '# WGs'
55+
# - status = Active
56+
active_wgs = wgs.filter(state__in=ACTIVE_STATES)
57+
58+
# - activity within last year? (use concluded_date)
59+
inactive_recent_wgs = wgs.exclude(state__in=ACTIVE_STATES).filter(time__gte=show_since)
60+
interesting_wgs = active_wgs | inactive_recent_wgs
61+
62+
for wg in interesting_wgs.distinct().iterator():
63+
name = wg.acronym
64+
dump_sublist('%s%s' % (name, '-ads'), get_group_ads_emails, wg, True)
65+
dump_sublist('%s%s' % (name, '-chairs'), get_group_chairs_emails, wg)
66+
67+
print '# RGs'
68+
# - status = Active
69+
rgs = Group.objects.filter(type='rg').all()
70+
active_rgs = rgs.filter(state__in=ACTIVE_STATES)
71+
72+
# - activity within last year? (use concluded_date)
73+
inactive_recent_rgs = rgs.exclude(state__in=ACTIVE_STATES).filter(time__gte=show_since)
74+
interesting_rgs = active_rgs | inactive_recent_rgs
75+
76+
for rg in interesting_rgs.distinct().iterator():
77+
name = rg.acronym
78+
#dump_sublist('%s%s' % (name, '-ads'), get_group_ads_emails, rg, True)
79+
dump_sublist('%s%s' % (name, '-chairs'), get_group_chairs_emails, rg)
80+
81+
# Additionally, for areaz, we should list -ads and -chairs
82+
# (for every chair in active groups within the area).
83+
print '# Areas'
84+
areas = Group.objects.filter(type='area').all()
85+
active_areas = areas.filter(state__in=ACTIVE_STATES)
86+
for area in active_areas:
87+
name = area.acronym
88+
dump_sublist('%s%s' % (name, '-ads'), get_area_ads_emails, area, True)
89+
dump_sublist('%s%s' % (name, '-chairs'), get_area_chairs_emails, area)
90+
91+

0 commit comments

Comments
 (0)