#!/usr/local/bin/python

# Copyright (c) 2002, Will Guaraldi
# 
# 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.
# --------------------------------------------------------------------------
"""
This script sends out New Day emails to various people I know.  It is
capable of handling content files in different ways:
  * as a stack - popping the next message off
  * as a loop - popping the next message off, but tossing it back at the
    end of the file
  * if it exists we use it, otherwise we ignore it

It sends emails to individual people via the smtplib.  It requires
a rc configuration file that looks something like this:

%<------------------------
# these are required for the script to run:
sethost        = host                # the hostname of your server
setemail       = emailaddress        # the email address of the sender
setrootdir     = filedir             # the directory holding all the content
setaddressfile = filename            # the file in the rootdir that holds
                                     # all the addresses

# these are optional:
setfooter  = somefooter message          # adds a line(s) to the footer
                                         # %% will be expanded to a CR
setsubject = somesubject message %DATE%  # sets the subject line
settest    = true|false                  # will put this in test mode

# and then any combination of the following three content lines
# where HEADER is the name of the content and filename is the file
# in the rootdir that holds the content.
stackfile  = HEADER:filename             # takes the top entry from the file
existsfile = HEADER:filename             # takes the whole file (if it exists)
loopfile   = HEADER:filename             # takes the top entry from the file
                                         # and then adds it back to the bottom
                                         # of the file
execfile   = HEADER:command line         # executes a command
%<------------------------
"""
import os, sys, time, smtplib, commands

VERCREDIT = "mailday v.2.0.2, last edited: 01/02/2002, wbg";

NONE = 0
STACKFILE = 1
LOOPFILE = 2

def parse_args(args):
  """
  Takes in a list of args and parses it out into a hashmap
  of arg-name to value(s).

  @param args: the list of command-line arguments
  @type  args: string

  @return: list of tuples of (arg, value) pairings
  @rtype: list of (string, string)
  """
  i = 0
  optlist = []
  while (i < len(args)):

    if args[i][0] == "-":
      if (i+1 < len(args)):
        if args[i+1][0] != "-":
          optlist.append((args[i], args[i+1]))
          i = i + 1
        else:
          optlist.append((args[i], ""))
      else:
        optlist.append((args[i], ""))

    else:
      optlist.append(("", args[i]))

    i = i + 1
  return optlist


def sendEmail(options, addresses, body):
  """
  Takes an email and sends it via the smtplib.

  @param options: the options map containing such diverse keys as
      "emailaddr", "host", and "subject".
  @type  options: map

  @param addresses: the list of email addresses to send to
  @type  addresses: list of strings

  @param body: the message to send
  @type  body: string
  """
  from time import strftime, localtime, ctime, time

  # this is kind of sketchy--we're extracting the actual email address
  # and separating it from the friendly part of the email address
  email = options["emailaddr"]
  friendlyemail = ""
  index = email.find("<")
  if index != -1:
    friendlyemail = email[:index]
    email = email[index+1:email.find(">")]

  mailserver = smtplib.SMTP(options["host"])
  for mem in addresses:
    try:
      message = (strftime("Date: %a, %d %b %Y %H:%M:%S -0600\n", localtime(time())) +
              "From: \"" + friendlyemail + "\" <" + email + ">\n" +
              "To: " + mem + "\n" +
              "Subject: " + options["subject"] + "\n" +
              "X-Mailer: mailday.py.2.0\n" +
              "Content-Type: text/plain\n" +
              "Content-Transfer-Encoding: 7bit\n" +
              "\n" +
              body)

      # remove 13s
      message = message.replace(chr(13), '')

      mailserver.sendmail(email, mem, message)
    except smtplib.SMTPException, e:
      print "Mail send failed.  SMTPException %s" % e

  mailserver.quit()

def removeComment(line):
  """
  Takes a line and removes commented out portions of the line through
  some funky magic stuff.

  @param line: the line in question
  @type  line: string

  @returns: the line without the commented out portion
  @rtype: string
  """
  if not line:
    return line

  if line[0] == "#":
    return ""

  index = line.find("#")
  if index != -1:
    # FIXME - we do this wrong--we should parse through it to verify
    # it's not embedded in a string
    line = line[:index]

  return line

def readrcfile(filename):
  """
  Reads through a mailday rc file, strips off comments, and accounts
  for dated materials as well leaving only the portions of the rcfile
  that are pertinent to today's run.
  
  @param filename: the name of the rcfile
  @type  filename: string

  @returns: the list of lines in the rcfile
  @rtype: list of strings
  """
  rcfile = []
  f = open(filename, "r")
  lines = f.read()
  f.close()
  
  for mem in lines.splitlines():
    mem = removeComment(mem)
    mem = mem.strip()

    # check for date restrictions and route out the ones that we ignore
    # FIXME

    # append whatever's left (if there is something) to the file list
    if mem:
      rcfile.append(mem)

  return rcfile

def readAddressFile(filename):
  """
  Opens an address file, removes commented out portions, and returns
  a list of addresses in the file.

  @param filename: the name of the file
  @type  filename: string

  @returns: list of address recipients
  @rtype: list of strings
  """
  f = open(filename, "r")
  lines = f.read()
  f.close()

  temp = []

  for mem in lines.splitlines():
    mem = removeComment(mem)
    mem = mem.strip()
    if mem:
      temp.append(mem)

  return temp

def getItemFromFile(filename, filetype=NONE):
  """
  If the file exists, then we retrieve the item.  If the file
  does not exist or it's empty, then we return an empty string.

  @param filename: the name of the file
  @type  filename: string

  @returns: the item or empty string
  @rtype: string
  """
  global STACKFILE, LOOPFILE

  try:
    f = open(filename, "r")
    data = f.read()
    f.close()

    data = data.split("\n%\n")
    item = data[0]

    if filetype == STACKFILE and len(data) > 0:
      data = data[1:]
      data = "\n%\n".join(data)
      f = open(filename, "w")
      f.write(data)
      f.close()

    elif filetype == LOOPFILE and len(data) > 1:
      data = data[1:]
      data.append(item.strip())
      data = "\n%\n".join(data)
      f = open(filename, "w")
      f.write(data)
      f.close()

  except:
    item = ""

  return item


def getNameValue(line):
  """
  Takes a line from an rcfile and splits it into a name value
  pair.  In the case of lines that aren't a name/value pair,
  it returns the name and the value 1.

  @param line: the line in question
  @type  line: string

  @returns: a tuple of the name/value pair
  @rtype: (string, string) or (string, 1)
  """
  index = line.find("=")
  if index != -1:
    return (line[:index].strip(), line[index+1:].strip())
  else:
    return (line.strip(), 1)


if __name__ == '__main__':
  optlist = parse_args(sys.argv[1:])
  options = {"rc": "maildayrc",
             "test": 0,
             "emailaddr": "",
             "host": "",
             "rootdir": "",
             "footer": "",
             "subject": "new mailing"}

  # read through command-line arguments
  for mem in optlist:
    if mem[0] == "--rc":
      options["rc"] = mem[1]

    elif mem[0] == "--test":
      options["test"] = 1

    else:
      print "unknown parameter on command line '%s'='%s'." % (mem[0], mem[1])
      sys.exit(0)

  print "using %s." % options["rc"]
  rcfile = readrcfile(options["rc"])

  message = []
  addresses = []

  for line in rcfile:
    name, value = getNameValue(line)
    if name == "setemail":
      # handle the setemail option
      options["emailaddr"] = value
      print "setting emailaddr '%s'." % value

    elif name == "sethost":
      options["host"] = value
      print "setting host '%s'." % value

    elif name == "setrootdir":
      options["rootdir"] = value
      print "setting rootdir '%s'." % value

    elif name == "setfooter":
      value = value.replace("%%", "\n")
      options["footer"] = value
      print "setting footer '%s'." % value

    elif name == "settest":
      # sets the test parameter
      if value == "true" or value == "yes" or value == 1:
        options["test"] = 1
      else:
        options["test"] = 0

    elif name == "setsubject":
      # handles setting the subject magically
      value = value.replace("%DATE%", time.strftime("%m-%d-%Y", time.localtime()))
      options["subject"] = value
      print "setting subject '%s'." % options["subject"]

    elif name == "stackfile":
      try:
        itemname, filename = value.split(":")
        item = getItemFromFile(options["rootdir"] + filename, STACKFILE)
        if item:
          message.append("]] " + itemname + " [[")
          message.append("")
          message.append(item)
          message.append("")
          message.append("")
          print "adding stack item from '%s'." % (options["rootdir"] + filename)
        else:
          print "no data in '%s'" % (options["rootdir"] + filename)
      except Exception, e:
        print e
        
    elif name == "execfile":
      try:
        itemname, filename = value.split(":")
        item = commands.getoutput(filename)
        if item:
          message.append("]] " + itemname + " [[")
          message.append("")
          message.append(item)
          message.append("")
          message.append("")
          print "adding output from '%s'." % (filename)
      except Exception, e:
        print e

    elif name == "existsfile":
      try:
        itemname, filename = value.split(":")
        item = getItemFromFile(options["rootdir"] + filename)
        if item:
          message.append("]] " + itemname + " [[")
          message.append("")
          message.append(item)
          message.append("")
          message.append("")
          print "adding exists item from '%s'." % (options["rootdir"] + filename)
      except Exception, e:
        print e
         
    elif name == "loopfile":
      try:
        itemname, filename = value.split(":")
        item = getItemFromFile(options["rootdir"] + filename, LOOPFILE)
        if item:
          message.append("]] " + itemname + " [[")
          message.append("")
          message.append(item)
          message.append("")
          message.append("")
          print "adding loop item from '%s'." % (options["rootdir"] + filename)
        else:
          print "no data in '%s'" % (options["rootdir"] + filename)
      except Exception, e:
        print e
        
    elif name == "setaddressfile":
      temp = readAddressFile(options["rootdir"] + value)
      print "adding addresses from '%s'." % (options["rootdir"] + value)
      for mem in temp:
        addresses.append(mem)

    else:
      print "unknown rcfile thing '%s'." % name


  if not options["emailaddr"]:
    print "setemail needs to be set in rc file.  quitting."
    sys.exit(0)

  if not options["host"]:
    print "sethost needs to be set in rc file.  quitting."
    sys.exit(0)

  if not addresses:
    print "no addresses to send to.  quitting."
    sys.exit(0)

  if not message:
    print "no message to send.  quitting."
    sys.exit(0)

  email = "\n".join(message) + "\n-----\n" 
  if options["footer"]:
    email += options["footer"] + "\n"
  email += VERCREDIT + "\n"

  if options["test"] == 0:
    print "sending emails."
    sendEmail(options, addresses, email)

  else:
    # print out testing stuff
    print "test"
    print "OPTIONS"
    for mem in options.keys():
      print "   " + mem + ": " + repr(options[mem])

    print ""
    print "ADDRESSES"
    print " ".join(addresses)
    print ""
    print "MESSAGE"
    print "Subject: " + options["subject"]
    print email

  print "done.  goodbye!"
