#! /usr/bin/env python

# Copyright (c) 2005-2007 Laurence Tratt
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.


import email, email.Generator, email.Utils, getopt, imp, os, popen2, sys, tempfile, time
from cStringIO import StringIO




PROG_NAME = "email_merger"
PROG_VERSION = "0.07 (2007/04/08)"
PROG_COPYRIGHT = "Copyright (C) Laurence Tratt 2005-2007"

CONFIG_PATH = "$HOME/.email_merger"

DEFAULT_SENDMAIL = "/usr/sbin/sendmail -oem -oi"
DEFAULT_POST_SENDMAIL_DELAY = 1




def usage(msg = None):

	if msg is None:
		msg = ""
	else:
		msg = msg + "\n\n"

	print """%s%s [-e] [-d] [-o <dir>] <template file> <substitution file>
%s

This program takes in a template e-mail message and a substition file. The
substitution file starts with a tab separated list of substitution names
which define keys which will be replaced in the template. Each subsequent
line of the substitution file specifies particular values for these keys.
Blank lines in the substituion file are ignored. A new e-mail will be
generated and sent for each line in the substitution file. 

The template file should be a properly formatted e-mail message e.g. one
or more headers followed by a blank line and then the message body.
Substitution can occur in both the header and message body.

Keys are identified in the template file by the syntax ${X} where X is
a name satisfying the following regular expression [_0-9a-zA-Z]+.

If the -e flag is specified, each substituted e-mail is then passed to
$EDITOR for editing. The user may edit any part of the e-mail, including
the headers.

After substitution and any subsequent editing has occurred, the following
headers are added to the outgoing e-mail if they are not present in after
substitution:

  Date: <date>
  User-Agent: %s %s

If a BCC header(s) is specified, it is stripped before the e-mail is sent.

The -d flag specifies a "dummy run". All activities are performed, but
no e-mails are sent. This is often combined with -e or -o.

The -o flag specifies that as e-mails are created, copies of are saved in
individual files in 'dir'. The -o flag can be used with either the -d or -e
modes of execution.

As %s runs, it prints out the addresses e-mails are being sent to.""" % (msg, PROG_NAME, PROG_COPYRIGHT, PROG_NAME, PROG_VERSION, PROG_NAME)
	sys.exit(1)




class Merger:

	def __init__(self):

		opts, args = getopt.getopt(sys.argv[1 : ], "edho:")

		self._sendmail = DEFAULT_SENDMAIL
		self._post_sendmail_delay = DEFAULT_POST_SENDMAIL_DELAY	
		config_path = os.path.expandvars(CONFIG_PATH)
		if os.path.exists(config_path):
			config = imp.load_source("config", config_path)
			if hasattr(config, "sendmail"):
				self._sendmail = config.sendmail
			if hasattr(config, "post_sendmail_delay"):
				self._post_sendmail_delay = config.post_sendmail_delay
	
		self._edit_emails = 0
		self._do_send = 1
		self._output_dir = None
		for opt in opts:
			if opt[0] == "-e":
				self._edit_emails = 1
			elif opt[0] == "-d":
				self._do_send = 0
			elif opt[0] == "-o":
				self._output_dir = opt[1]
				if not os.path.isdir(self._output_dir):
					self._error("Directory '%s' does not exist or is not a directory." % self._output_dir)
			elif opt[0] == "-h":
				usage()
			else:
				usage("Error: Unknown arg '%s'" % opt[0])
		
		if len(args) != 2:
			usage("Error: Not enough args")
		
		self._read_template_file(args[0])
		self._read_substitution_file(args[1])
		
		if self._output_dir is not None:
			dummy_files = []
		
		count = 0
		for values in self._substitution_values:
			substituted = self._substitute(values)
			msg = self._make_email(substituted)
			
			if self._edit_emails == 1:
				msg_string = self._email_to_string(msg)

				tf_fno, tf_path = tempfile.mkstemp()
				os.write(tf_fno, msg_string)
				os.close(tf_fno)
				rtn = os.system("$EDITOR %s" % tf_path)
				if rtn != 0:
					self._error("$EDITOR returned %d." % (path, rtn))
				file = open(tf_path, "r")
				msg_string = file.read(-1)
				file.close()
				
				msg = email.message_from_string(msg_string)

			if self._output_dir is not None:
				msg_string = self._email_to_string(msg)
				path = os.path.join(self._output_dir, `count + 1`)
				if os.path.exists(path):
					self._error("File '%s' already exists." % path)
				output_file = open(path, "w")
				output_file.write(msg_string)
				output_file.close()
			
			all_sender_addresses = self._get_sender_addresses(msg)
			all_sender_addresses_emails = [x[1] for x in all_sender_addresses]
			print ", ".join(all_sender_addresses_emails)

			if msg.has_key("BCC"):
				del msg["BCC"]

			if self._do_send:
				msg_string = self._email_to_string(msg)
				sendmail = popen2.Popen3("%s %s" % (self._sendmail, " ".join(all_sender_addresses_emails)), 1)
				sendmail.tochild.write(msg_string)
				sendmail.tochild.close()
				rtn = sendmail.wait()
				if rtn != 0:
					self._error("%s returned %d" % (self._sendmail, rtn))
				time.sleep(self._post_sendmail_delay)
			
			count += 1



	def _error(self, msg):
	
		print msg
		sys.exit(1)



	def _read_template_file(self, template_path):
	
		tf = open(template_path, "r")
		self._template = tf.read(-1)
		tf.close()



	def _read_substitution_file(self, substitution_path):
	
		sf = open(substitution_path, "r")
		
		self._substitution_keys = [x.strip() for x in sf.readline().split("\t")]

		# Line number count begins at 2 since line 1 is the substitution keys
		i = 2
		self._substitution_values = []
		for l in sf.readlines():
			if l.strip() == "":
				continue
			values = [x.strip() for x in l.strip().split("\t")]
			if len(values) != len(self._substitution_keys):
				self._error("Incorrect number of values at line %s" % `i`)
			self._substitution_values.append(values)
			i += 1



	def _substitute(self, values):
	
		substituted = self._template
		i = 0
		for i in range(0, len(self._substitution_keys)):
			substituted = substituted.replace("${%s}" % self._substitution_keys[i], values[i])
		
		return substituted



	def _make_email(self, substituted):
	
		msg = email.message_from_string(substituted)
		if "Date" not in msg:
			msg["Date"] = time.strftime("%a, %d %b %Y %H:%M:%S %z")
		if "User-Agent" not in msg:
			msg["User-Agent"] = "%s %s" % (PROG_NAME, PROG_VERSION)
		
		return msg



	def _email_to_string(self, msg):
	
		fp = StringIO()
		g = email.Generator.Generator(fp, mangle_from_=False, maxheaderlen=60)
		g.flatten(msg)
		
		return fp.getvalue()



	def _get_sender_addresses(self, msg):

		tos = msg.get_all("to", [])
		ccs = msg.get_all("cc", [])
		bccs = msg.get_all("bcc", [])
		resent_tos = msg.get_all("resent-to", [])
		resent_ccs = msg.get_all("resent-cc", [])
		
		return email.Utils.getaddresses(tos + ccs + bccs + resent_tos + resent_ccs)




if __name__ == "__main__":
	Merger()
