from collections import namedtuple
import datetime
import urllib

import pytz


class Post(namedtuple('Post', ['subject', 'author', 'body', 'date'])):
    """Represents a single Listserve email post.

    subject, author, and body are unicode strings.
    date is a 3-tuple of ints: (year, month, day).

    Posts are json-serializable.
    """

    def __new__(cls, subject, author, body, date):
        #If date is a list, make it a tuple.
        #This makes post == Post(*json.loads(json.dumps(post))).
        if isinstance(date, list):
            date = tuple(date)

        return super(cls, Post).__new__(
            cls,
            subject,
            author,
            body,
            date)

    @staticmethod
    def from_cio_message(message):
        """Post factory from a Context.IO message (with body).

        See http://context.io/docs/2.0/accounts/messages (and
        _include_body_)."""

        subject = message['subject']

        m_from = message['addresses']['from']
        author = m_from['name'] if 'name' in m_from else 'Anonymous'

        body = message['body'][0]['content']  # TL sends one plaintext body.
        #Remove unsubscribe text.
        body = body[:body.rfind('--')]

        date = datetime.datetime.fromtimestamp(message['date'])
        #This is a hack. Apparently, the posts aren't sent out automatically,
        #but manually sent on a schedule aligned with EST. We can count
        #on not getting more than one post in a day, but the definition of
        #day aligns with EST. Convert to EST and allow for a few hours of
        #leeway past midnight.
        eastern = pytz.timezone("US/Eastern")
        date = eastern.localize(date)
        date = date - datetime.timedelta(hours=4)
        date = (date.year, date.month, date.day)

        return Post(subject, author, body, date)

    def datestr(self):
        """Return the date of this Post as 'YYYY-MM-DD'."""
        strs = [str(i) for i in self.date]

        #Prepend 0s where needed, assuming year is length 4.
        return '-'.join('0' * (2 - len(s)) + s for s in strs)

    def to_jekyll_html(self):
        """Return a Jekyll post as (filename, contents)."""

        #Build a readable datestr, eg 'August 02 2012'.
        date = datetime.date(*self.date)
        full_month_datestr = date.strftime("%B %d %Y")
        datestr_with_comma = date.strftime("%B %d, %Y")

        #The post subject becomes the page title and description.
        page_title = self.subject.replace('[The Listserve]', '').strip()
        if not page_title:
            page_title = '[no subject]'
            desc_title = page_title
        else:
            desc_title = '"%s"' % page_title

        desc = "The Listserve post on %s: %s" % (datestr_with_comma, desc_title)

        #Jekyll needs the filename as YYYY-MM-DD-title.markup
        #title can be empty, but we still need the '-'
        fname = "{date}-{page_title}.html".format(
            date=self.datestr(),
            page_title=urllib.quote_plus(page_title.encode('utf-8'))
        )

        #Find paragraphs.
        post_text = self.body.replace('\r', '')
        paras = post_text.split(u'\n\n')
        paras = [para.replace('\n', '<br />') for para in paras if para]

        #Build html paragraphs.
        post_text = '\n'.join(
            ["<p>%s</p>" % para.encode('ascii', 'xmlcharrefreplace')
             for para in paras])

        #Encode output and build file contents.
        #TODO this is clearly vulnerable to code injection
        contents = """---
layout: post
title: "{page_title}"
description: "{desc}"
---

<h2 id='post-title'>
{{{{ page.title }}}}
</h2>

<p class="meta">{date}</p>

{post_text}""".format(page_title=page_title.replace('"', r'\"').encode('utf-8'),
                      desc=desc.replace('"', r'\"').encode('utf-8'),
                      date=full_month_datestr,
                      post_text=post_text
                      )

        return (fname, contents)

@@ -28,16 +28,27 @@
 edtuple%0A
+import cgi%0A
 import d
@@ -3186,24 +3186,25 @@
 '%5Cn%5Cn')%0A
+%0A
         
 paras = 
@@ -3199,45 +3199,133 @@
     
-paras = %5Bpara.replace('%5Cn', '%3Cbr /%3E')
+#Build html paragraphs.%0A        paras = %5B%22%3Cp%3E%25s%3C/p%3E%22 %25 cgi.escape(para).encode('ascii', 'xmlcharrefreplace')%0A                
  for
@@ -3348,25 +3348,24 @@
 f para%5D%0A
-%0A
         
 #Build h
@@ -3356,39 +3356,72 @@
         
-#Build html paragraphs.
+paras = %5Bpara.replace('%5Cn', '%3Cbr /%3E') for para in paras%5D
 %0A       
@@ -3443,16 +3443,23 @@
 n'.join(
+paras)%0A
 %0A       
@@ -3463,209 +3463,227 @@
     
-    %5B%22%3Cp%3E%25s%3C/p%3E%22 %25 para.encode('ascii', 'xmlcharrefreplace')%0A             for para in paras%5D)%0A%0A        #Encode output and build file contents.%0A        #TODO this is clearly vulnerable to code injection
+property_escape = lambda s: cgi.escape(s, True).encode('ascii', 'xmlcharrefreplace')%0A        page_title = property_escape(page_title)%0A        desc = property_escape(desc)%0A%0A        #Encode output and build file contents.
 %0A   
@@ -3701,16 +3701,18 @@
 ts = %22%22%22
+%5C%0A
 ---%0Alayo
@@ -3893,113 +3893,41 @@
 itle
-.replace('%22', r'%5C%22').encode('utf-8'),%0A                      desc=desc.replace('%22', r'%5C%22').encode('utf-8')
+,%0A                      desc=desc
 ,%0A  

fix escaping