Index: fedd/access_to_abac.py
===================================================================
--- fedd/access_to_abac.py	(revision 6cc5c8121e0657cb88b97caa040b4d27d5fd8c58)
+++ fedd/access_to_abac.py	(revision 6cc5c8121e0657cb88b97caa040b4d27d5fd8c58)
@@ -0,0 +1,190 @@
+#!/usr/local/bin/python
+
+import sys, os
+import re
+
+from federation.fedid import fedid
+from optparse import OptionParser, OptionValueError
+
+class attribute:
+    def __init__(self, p, a):
+	self.principal = p
+	self.attr = a
+
+    def __str__(self):
+	return "%s.%s" % (self.principal, self.attr)
+
+class credential:
+    def __init__(self, p, a, req):
+	self.principal = p
+	if isinstance(a, (tuple, list, set)) and len(a) == 1:
+	    self.attr = a[0]
+	else:
+	    self.attr = a
+	self.req = req
+
+    def __str__(self):
+	if isinstance(self.req, (tuple, list, set)):
+	    return "%s.%s <- %s" % (self.principal, self.attr, 
+		    " & ".join(["%s" % r for r in self.req]))
+	else:
+	    return "%s.%s <- %s" % (self.principal, self.attr, self.req)
+
+class parse_error(RuntimeError): pass
+
+def parse_emulab(l, creds, me, to_id, p, gp, gu):
+    right_side_str = '\s*,\s*\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
+	    (id_same_str, id_same_str,id_same_str)
+
+    m = re.match(right_side_str, l)
+    if m:
+	project, user = m.group(1,2)
+	if project == '<same>':
+	    if gp  is not None:
+		project = gp 
+	    else:
+		raise parse_error("Project cannot be decisively mapped: %s" % l)
+		return False
+	if user == '<same>':
+	    if gu is not None:
+		project = gu 
+	    else:
+		raise parse_error("User cannot be decisively mapped: %s" % l)
+		return False
+	if project and user:
+	    a = 'project_%s_user_%s' % (project, user)
+	elif project:
+	    a = 'project_%s' % project
+	elif user:
+	    a = 'user_%s' % user
+	else:
+	    raise parse_error("No mapping for %s/%s!?" % (gp, gu))
+	    return False
+
+	c = credential(me, a, 
+		[attribute(p, x) for x in (gp, gu) if x is not None])
+	creds.add(c)
+	if a in to_id: to_id[a].append(c)
+	else: to_id[a] = [ c ]
+	return True
+    else:
+	raise parse_error("Badly formatted local mapping: %s" % l)
+	return False
+
+
+def parse_dragon(l, creds, me, to_id, p, gp, gu):
+    right_side_str = '\s*,\s*\(\s*(%s)\s*\)' % \
+	    (id_str)
+
+    m = re.match(right_side_str, l)
+    if m:
+	repo= m.group(1)
+	c = credential(me, 'repo_%s' % repo, 
+		[attribute(p, x) for x in (gp, gu) if x is not None])
+	creds.add(c)
+	if repo in to_id: to_id[repo].append(c)
+	else: to_id[repo] = [ c ]
+	return True
+    else:
+	raise parse_error("Badly formatted local mapping: %s" % l)
+	return False
+
+
+class access_opts(OptionParser):
+    mappers = { 'emulab': parse_emulab, 'dragon': parse_dragon }
+
+    @staticmethod
+    def parse_mapper(opt, s, val, parser, dest):
+	if val in access_opts.mappers:
+	    setattr(parser.values, dest, access_opts.mappers[val])
+	else:
+	    raise OptionValueError('%s must be one of %s' % \
+		    (s, ", ".join(access_opts.mappers.keys())))
+
+    def __init__(self):
+	OptionParser.__init__(self, usage='%prog [opts] file [...]')
+	self.add_option('--cert', dest='cert', default=None,
+		help='my fedid as an X.509 certificate')
+	self.add_option('--key', dest='key', default=None,
+		help='key for the certificate')
+	self.add_option('--type', action='callback', nargs=1, type='str',
+		callback=access_opts.parse_mapper, 
+		callback_kwargs = { 'dest': 'mapper'}, 
+		help='Type of access file to parse.  One of %s. ' %\
+			", ".join(access_opts.mappers.keys()) + \
+			'Omit for generic parsing.')
+	self.set_defaults(mapper=None)
+
+
+
+comment_re = re.compile('^\s*#|^$')
+fedid_str = 'fedid:([0-9a-fA-F]{40})'
+id_str = '[a-zA-Z][\w-]*'
+id_any_str = '(%s|<any>)' % id_str
+id_same_str = '(%s|<same>)' % id_str
+left_side_str = '\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
+	(fedid_str, id_any_str, id_any_str)
+right_side_str = '(%s)(\s*,\s*\(.*\))?' % (id_str)
+line_re = re.compile('%s\s*->\s*%s' % (left_side_str, right_side_str))
+
+p = access_opts()
+opts, args = p.parse_args()
+
+if len(args) < 1:
+    sys.exit('No filenames given to parse')
+
+if opts.cert: 
+    try:
+	me = fedid(file=opts.cert) 
+    except EnvironmentError, e:
+	sys.exit('Bad --cert: %s (%s)' % (e.strerror, e.filename or '?!'))
+else: 
+    print >>sys.stderr, 'No --cert, using dummy fedid'
+    me = fedid(hexstr='0123456789012345678901234567890123456789')
+
+mapper = opts.mapper
+
+for fn in args:
+    creds = set()
+    to_id = { }
+    try:
+	f = open(fn, "r")
+	for i, l in enumerate(f):
+	    try:
+		if comment_re.match(l):
+		    continue
+		else:
+		    m =  line_re.match(l)
+		    if m:
+			p, da = m.group(1, 4)
+			gp, gu = m.group(2, 3)
+			if gp == '<any>': gp = None
+			if gu == '<any>': gu = None
+
+			creds.add(credential(me, da, 
+				[attribute(p, x) for x in (gp, gu) \
+				    if x is not None]))
+			if m.group(5) and mapper:
+			    mapper(m.group(5), creds, me, to_id, p, gp, gu)
+		    else:
+			raise parse_error('Syntax error')
+	    except parse_error, e:
+		raise parse_error('Error on line %d of %s: %s' % \
+			(i, fn, e.message))
+		
+	f.close()
+    except parse_error, e:
+	print >> sys.stderr, "%s" % e
+	continue
+
+    except EnvironmentError, e:
+	print >>sys.stderr, "File error %s: %s" % \
+		(e.filename or '!?', e.strerror) 
+	continue
+
+    for c in creds:
+	print "%s" % c
+
+    for k, c in to_id.items():
+	print "%s: %s" % ( k , ", ".join(set(["%s.%s" % (x.principal, x.attr) \
+		for x in c])))
