Mercurial > hg > python
view mailer.py @ 55:68004ce55703
convert to newer source of Nonogram data
author | Henry Thompson <ht@markup.co.uk> |
---|---|
date | Mon, 29 May 2023 22:24:53 +0100 |
parents | 10f17205908f |
children |
line wrap: on
line source
#!/usr/bin/python2.7 '''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]*] [-u] [-p COLPAT]* [-SA file[,file]*] [-R reply-to] [-f from] [-m .sigfilename] 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 -R for replyTo address -f for fromName, defaults to $USER@$HOSTNAME -m for .sig file, None for none 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, 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 replyTo=None sigfile=None fromName='%s@%s'%(os.getenv('USER'),os.getenv('HOSTNAME')) 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]=='-R': sys.argv.pop(0) if sys.argv: replyTo=sys.argv.pop(0) else: usage('reply to') elif sys.argv[0]=='-f': sys.argv.pop(0) if sys.argv: fromName=sys.argv.pop(0) else: usage('from name') elif sys.argv[0]=='-m': sys.argv.pop(0) if sys.argv: sigfile=sys.argv.pop(0) else: usage('sig file name') 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') signature=None if sigfile!='None': if sigfile is None: sigfile="/home/ht/.signature" try: sig=open(sigfile,"r") signature=sig.read().rstrip() except: if sigfile!="/home/ht/.signature": raise CS=', ' body=bodyFile.read().rstrip() if not dryrun: mailer=smtplib.SMTP() mailer.connect() for l in addrFile: addrFields=l.rstrip().split('\t') if doSub: try: bodyPlus=body%subDict(addrFields) except KeyError: print("can't subDict",repr(subDict(addrFields)), body) usage('-S') 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 replyTo is not None: msg["Reply-To"]=replyTo if dryrun: print recips print msg.keys() print 'From: %s'%fromName print msg.as_string() exit() print "mailing to %s"%recips mailer.sendmail(fromName,recips,msg.as_string()) mailer.quit()