Mercurial > hg > python
diff mailer.py @ 2:e07789816ca5
adding more python files from lib/python on origen
author | Henry Thompson <ht@markup.co.uk> |
---|---|
date | Mon, 09 Mar 2020 16:48:09 +0000 |
parents | |
children | 2d7c91f89f6b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mailer.py Mon Mar 09 16:48:09 2020 +0000 @@ -0,0 +1,415 @@ +#!/usr/bin/python +'''Attempt at flexible mailout functionality +Usage: mailer.py [-n] [-s] [-C cc string] [-c COLSPEC[,COLSPEC]*] [-B bcc string] [-b COLSPEC[,COLSPEC]*] [-S col[,col]*] [-a COLSPEC[,COLSPEC]*] [-p COLPAT]* -SA file[,file]* COLSPEC[,COLSPEC]* subject {addr-file|-} body-file + +Sends the body as a message from me with subject to destinations per +lines in the addr-file selected by COLSPECs (to:) or -c/-b COLSPECs (Cc:/Bcc:) + +-n for dry run, prints to stdout +-c for Cc column(s) +-C for static Cc +-b for Bcc columns(s) +-B for static Bcc +-a for attachment file column(s) +-A for attachment file pattern column(s) +-SA for static attachment files +-u Use unicode for attachments +-s for substitute into body +-S for columns to substitute as such +-p for augmentation pattern for a column + +COLSPEC is of the form a[:n[:f[:g]]] selects from addr-file, which must be tsv + a gives the column for an email address + n (optional) gives column for a name + f gives format for the name: FS, SF or S.F for + forenames surname (fornames space separated) + surname forenames (space separated) + surname, forenames (space separated) + default is FS + _ will be replaced by space in surnames + g gives column for gender (for pronouns), m or f +COLPAT takes the form i:template, where i selects an address column +and template is a string containing exactly 1 "%s", which is replaced with +the column value to give the string which will be used for COLSPEC +references to that column, e.g. 1:S%s@sms.ed.ac.uk +if column 1 contains bare student numbers + +-s enables body substitution. body may contain + %(fi)s first forename of column i + %(si)s surname + %(fsi)s all forenames + %(i)s the undivided original and/or -S col value + if there is a supplied gender + %(pni)s 'he'/'she' + %(pai)s 'him'/'her' + %(pgi)s 'his/her' + +All column indices are 1-origin, as for cut''' + +import smtplib, sys, re, os.path, codecs +from email.mime.text import MIMEText + +addrPat=re.compile("<([^>]*)>") + +def usage(hint=None): + if hint is None: + print __doc__ + exit() + else: + print >>sys.stderr,"Trouble with your commandline at %s\n %s"%(hint, + __doc__) + exit(1) + +def parseCols(specs,where): + return [Column(s,where) for s in specs.split(',')] + +def parsePat(spec): + (c,t)=spec.split(':') + c=int(c) + found=False + for colTab in (ccCols,bccCols,toCols,attCols): + if c in colTab: + colTab[c].addTemplate(t) + found=True + if not found: + print >>sys.stderr, "Warning, template supplied for column %s, but no use of the column found!"%c + +def addrList(addrFields,cols,att=False): + global someExpand + if att and someExpand: + # There were some file patterns + return itertools.chain(*(c.fullAddr(addrFields,True) for c in cols.values())) + else: + return [c.fullAddr(addrFields) for c in cols.values()] + +def addrLine(hdr,addrFields,cols): + return "%s: %s"%(hdr,", ".join(addrList(addrFields,cols))) + +def subDict(addrFields): + res={} + for c in names.values(): + c.subDo(addrFields,res) + for c in subs.values(): + if c not in names: + c.subDo(addrFields,res) + return res + +bccCols={} +ccCols={} +attCols={} +toCols={} +names={} +subs={} +CC=[] +BCC=[] +rawCols={} + +class Column: + _expand=False + def __init__(self,spec,where): + global names, subs + parts=spec.split(':') + if (len(parts)<1 or len(parts)>4): + print >>sys.stderr, "col spec. must have 1--4 :-separated parts: %s"%parts + usage('colspec') + self.a=int(parts[0]) + if len(parts)>1: + self.n=int(parts[1]) + if len(parts)>2: + self.f=parts[2] + else: + self.f='FS' + if len(parts)>3: + self.g=int(parts[3]) + else: + self.g=None + else: + self.n=None + if self.a<=0: + print >>sys.stderr, "addr column index %s not allowed -- 1-origin indexing"%self.a + exit(2) + if self.a in where: + print >>sys.stderr, "duplicate column %s"%self.a + exit(2) + if self.n is not None: + if self.n<=0: + print >>sys.stderr, "name column index %s not allowed -- 1-origin indexing"%self.n + exit(3) + if self.n in where: + print >>sys.stderr, "can't use column %s as both name and address"%self.n + exit(3) + if self.n in names: + print >>sys.stderr, "attempt to redefine %s from \"%s\" to \"%s\""%(self.n,names[self.n],self) + exit(3) + if self.f not in ('FS','SF','S.F'): + print >>sys.stderr, "name format %s not recognised"%self.f + exit(4) + where[self.a]=self + if self.n is not None: + if isinstance(self,RawColumn): + subs[self.n]=self + else: + names[self.n]=self + + def __str__(self): + if self.n is None: + return str(self.a) + else: + return "%s:%s"%(self.a,self.n) + + def __repr__(self): + return str(self) + + def addTemplate(self,template): + try: + print >>sys.stderr,"Attempt to overwrite existing template \"%s\" for %s with \"%s\""%(self.template, + self.a, + template) + except AttributeError: + self.template=template + + def name(self): + return self.n + + def expAddr(self,fields): + addr=fields[self.a-1] + try: + return self.template%addr + except AttributeError: + return addr + + def fullAddr(self,fields,att=False): + global someExpand + if self.n is None: + res=self.expAddr(fields) + if att and someExpand: + if self._expand: + return glob.iglob(res) + else: + return [res] + else: + return res + else: + return '"%s" <%s>'%(fields[self.n-1].replace('_',' '),self.expAddr(fields)) + + def subDo(self,addrFields,dict): + f=addrFields[self.n-1] + dict[str(self.n)]=f + nparts=f.split(' ') + if self.f=='FS': + sur=nparts.pop() + elif self.f=='SF': + sur=nparts.pop(0) + elif self.f=='S.F': + sur=nparts.pop(0)[:-1] + fores=nparts + dict['fs%s'%self.n]=' '.join(fores) + dict['f%s'%self.n]=fores[0] + dict['s%s'%self.n]=sur.replace('_',' ') + if self.g is not None: + gg=addrFields[self.g-1] + if gg=='m': + dict['pn%s'%self.n]='he' + dict['pa%s'%self.n]='him' + dict['pg%s'%self.n]='his' + elif gg=='f': + dict['pn%s'%self.n]='she' + dict['pa%s'%self.n]='her' + dict['pg%s'%self.n]='her' + else: + print >>sys.stderr,"Warning, unrecognised gender in column %s: %s"%(self.n,gg) + + def setExpand(self): + self._expand=True + +class RawColumn(Column): + '''Not for person names, just raw text''' + + def subDo(self,addrFields,dict): + f=addrFields[self.n-1] + dict[str(self.n)]=f + +def doAtt(msg,att,codec): + (mt,enc)=mimetypes.guess_type(att) + (tp,subtp)=mt.split('/',2) + if tp=='text': + attf=codecs.open(att,'r',codec) + atm=MIMEText(attf.read(),subtp,codec) + elif tp=='application': + from email.mime.application import MIMEApplication + attf=open(att,'r') + atm=MIMEApplication(attf.read(),subtp) + else: + print >>sys.stderr, "Help: Media type %s (for attachment %s) not supported"%(mt,att) + exit(5) + atm.add_header('Content-Disposition','attachment', + filename=os.path.basename(att)) + msg.attach(atm) + +dryrun=False +sys.argv.pop(0) +doSub=False +pats=[] +someExpand=False +codec='iso-8859-1' +staticAtts=[] +while sys.argv: + if sys.argv[0]=='-n': + dryrun=True + sys.argv.pop(0) + elif sys.argv[0]=='-c' and ccCols=={}: + sys.argv.pop(0) + if sys.argv: + parseCols(sys.argv.pop(0),ccCols) + else: + usage('cc') + elif sys.argv[0]=='-C' and CC==[]: + sys.argv.pop(0) + if sys.argv: + CC=sys.argv.pop(0).split(',') + else: + usage('CC') + elif sys.argv[0]=='-b' and bccCols=={}: + sys.argv.pop(0) + if sys.argv: + parseCols(sys.argv.pop(0),bccCols) + else: + usage('bcc') + elif sys.argv[0]=='-B' and BCC==[]: + sys.argv.pop(0) + if sys.argv: + BCC=sys.argv.pop(0).split(',') + else: + usage('BCC') + elif sys.argv[0] in ('-a','-A','-SA'): # and attCols=={} + expand=sys.argv[0]=='-A' + static=sys.argv[0]=='-SA' + sys.argv.pop(0) + if sys.argv: + if static: + staticAtts=sys.argv.pop(0).split(',') + else: + pc=parseCols(sys.argv.pop(0),attCols) + if expand: + import itertools, glob + someExpand=True + for c in pc: + c.setExpand() + from email.mime.multipart import MIMEMultipart + import mimetypes + else: + usage('attachment') + elif sys.argv[0]=='-u': + sys.argv.pop(0) + codec='utf-8' + elif sys.argv[0]=='-s': + sys.argv.pop(0) + doSub=True + elif sys.argv[0]=='-S' and rawCols=={}: + sys.argv.pop(0) + if sys.argv: + for c in sys.argv.pop(0).split(','): + RawColumn("%s:%s"%(c,c),rawCols) + else: + usage('raw subs') + elif sys.argv[0]=='-p': + sys.argv.pop(0) + if sys.argv: + pats.append(sys.argv.pop(0)) + else: + usage('pat') + elif sys.argv[0][0]=='-': + print sys.argv + usage() + else: + break + +if sys.argv: + parseCols(sys.argv.pop(0),toCols) +else: + usage('to') + +pats=[parsePat(p) for p in pats] + +if sys.argv: + subj=sys.argv.pop(0) +else: + usage('subj') + +if sys.argv: + af=sys.argv.pop(0) + if af=='-': + addrFile=sys.stdin + else: + try: + addrFile=open(af,'r') + except: + usage('addr: %s'%sys.exc_value) +else: + usage('addr') + +if sys.argv: + bf=sys.argv.pop(0) + try: + bodyFile=open(bf,'r') + except: + usage('body: %s'%sys.exc_value) +else: + usage('body') + +try: + sig=open("/home/ht/.signature","r") + signature=sig.read().rstrip() +except: + signature=None + +CS=', ' +body=bodyFile.read().rstrip() +if not dryrun: + mailer=smtplib.SMTP() + mailer.connect() +for l in addrFile: + addrFields=l.rstrip().split('\t') + if doSub: + bodyPlus=body%subDict(addrFields) + else: + bodyPlus=body + if signature is not None: + bodyPlus+="\n--\n" + bodyPlus+=signature + if attCols or staticAtts: + msg=MIMEMultipart() + msg.attach(MIMEText(bodyPlus)) + else: + msg=MIMEText(bodyPlus) + #to=addrLine("To",addrFields,toCols) + to=addrList(addrFields,toCols) + #msg=to + #recips=addrPat.findall(to) + msg['To']=CS.join(to) + recips=[]+list(to) + cc=CC + if ccCols: + cc+=addrList(addrFields,ccCols) + if cc!=[]: + msg["Cc"]=CS.join(cc) + recips+=list(cc) + bcc=BCC + if bccCols: + bcc+=addrList(addrFields,bccCols) + if bcc!=[]: + msg["Bcc"]=CS.join(bcc) + recips+=list(bcc) + msg["Subject"]=subj + for att in staticAtts: + doAtt(msg,att,codec) + if attCols: + for att in addrList(addrFields,attCols,True): + doAtt(msg,att,codec) + if dryrun: + print recips + print msg.as_string() + exit() + print "mailing to %s"%recips + mailer.sendmail("ht@inf.ed.ac.uk",recips,msg.as_string()) +mailer.quit()