import os
import os.path
import re
import time

# The base directory, where blog files are in.
blogdir = '/home/tom/idealog/blog'

# The directory of the web site.
webdir = '/home/tom/WWW.blog'

# The directory of the comments.
commentdir = '/home/tom/idealog/comments'

# The number of posts on the front page.
front_page_posts = 10

##############################################################################

# The current spoiler (must be unique in a page.)
spoiler = 0

def txt2html(text):
    """
    Formats text into html. The text is in my private format. It tries
    hard to get tag nesting right.
    
    @param text: The text to be formatted.
    
    @return: HTML, corresponding to the text.
    """


    global sqstore
    global spoiler

    def link(m):
        via = m.group(1) or "&rarr; "
        via = via.replace(" ", "&nbsp;")

        url = m.group(2)

        url = url.replace('&', '&amp;')
        url = url.replace('<', '&lt;')
        url = url.replace('>', '&gt;')
        url = url.replace('"', '&quot;')

        shorturl = url
        if len(url) > 60:
            shorturl = url[:60] + "..."

        return '<span class="link">%s<a href="%s">%s</a></span><br />' % (
            via, url, shorturl)

    text = re.compile(r"^(\w+ )?(http://\S+)", re.MULTILINE).sub(link, text)

    # This is used to collect the contents of the body.
    body = ""

    # If 0, automatic paragraph generation is enabled.
    noautop = 0

    # If 0, the backslash escapes should be processed.
    nobackslash = 0

    # True, if we're inside a paragraph.
    inp = False

    # The current state of the smartquotes. True if we're in a quote.
    sqstate = False

    # The position we're lexing at in the text.
    pos = 0

    # A lexer loop. We repeatedly apply regular expressions to the
    # current position in the text string, and then apply the options
    # that match to the start.
    #
    # Perhaps this should be refactored into multiple functions, but
    # passing a bunch of data around would be quite annoying.
    while pos < len(text):

        # Tags.
        m = re.compile(r"<(/?\w+).*?>", re.DOTALL).match(text, pos)
        if m:
            pos = m.end()

            tag = m.group(0)
            output = tag
            tagname = m.group(1).lower()

            # If true, this tag will not start a paragraph. Otherwise,
            # a <p> will be added before the tag.
            nostartp = False

            # If true, this tag will force a </p> if a paragraph is
            # open, and set inp to False.
            forcestopp = False

            # This is ored into inp. It should be set to true if the
            # tag opens a paragraph and leaves it open.
            manualp = False

            # The following conditionals look up tags, and set the
            # appropriate flags on them.
            if tagname == "pre":
                noautop += 1
                nobackslash += 1
                forcestopp = True
                   
            if tagname == "/pre":
                noautop -= 1
                nobackslash -= 1
                forcestopp = True
                nostartp = True

            if tagname == "table":
                noautop += 1
                forcestopp = True

            if tagname == "/table":
                noautop -= 1
                nostartp = True
                forcestopp = True

            if tagname in ( "blockquote", "/blockquote", "div", "/div" ):
                nostartp = True
                forcestopp = True

            if tagname == "spoiler":
                forcestopp = True
                nostartp = True
                manualp = True

                spoiler += 1
                output = """<div id="spoiler_%d" class="spoiler"><p><span class="show_spoiler">(<a href="javascript:show_spoiler('spoiler_%d');">show</a>)</span>""" % (spoiler, spoiler)

            if tagname == "/spoiler":
                forcestopp = True
                nostartp = True

                output = "</div>"
            
            # Decide what we want to do about paragraphs.

            if (not noautop) and (not nostartp) and (not inp):
                body += "<p>\n"
                inp = True

            if (forcestopp or noautop) and inp:
                body += "</p>\n"
                inp = False

            # Finally, output the tag itself.
            body += output

            inp |= manualp

            continue

        # Blank line forces paragraph close.
        m = re.compile(r"\n\s*\n").match(text, pos)
        if m:
            pos = m.end()
            if inp:
                body += "\n</p>\n"
                inp = False
            else:
                body += m.group(0)

            continue

        # Single newlines are just kicked out to stdout.
        if text[pos] == "\n":
            pos += 1
            body += "\n"

        # Everything matched below this point is basically textual and should
        # cause a paragraph to be started, if appropriate.
        if not noautop and not inp:
            body += "<p>\n"
            inp = True

        # Backslash-commands.
        m = re.compile(r"\\(.?)", re.I).match(text, pos)
        if not nobackslash and m:
            pos = m.end()
            c = m.group(1)

            if c == "&":
                body += "&amp;"
            elif c == "<":
                body += "&lt;"
            elif c == ">":
                body += "&gt;"
            elif c == '"':
                # Smart quoting.
                
                sqstate = not sqstate

                if sqstate:
                    body += '"<span class="quote">'
                else:
                    body += '</span>"'                    
            else:
                body += c

            continue
        
        elif m:
            pos = m.end()
            body += m.group(0)

            continue


        # Other text: Doesn't contain newlines, backslashes, or <s.
        m = re.compile(r"[^\n\\<]+").match(text, pos)
        if m:
            pos = m.end()
            body += m.group(0)

            continue

    if inp:
        body += "\n</p>"

    return body


page_template = """\
<html>
<head>
<title>IF -- %(title)s</title>

<link rel="stylesheet" href="/style.css" />
<link rel="alternate" title="RSS" type="application/rss+xml" href="/rss.xml" />
<link rel="shortcut icon" type="img/png" href="/favicon.ico" />

<script>
<!--
function show_spoiler(name) {
    document.getElementById(name).style.visibility = "visible";
}
// -->
</script>

</head>

<body>
<table rows=%(rowcount)d cols=2 class="front" cellspacing=0 width="100%%" height="100%%">
<tr><td height="1" width="75%%" class="blogtitle" valign=top><h1>Intermediate Form</h1></td>
<td class="frontright" width="25%%" valign=top rowspan=21>
%(rightnav)s
<div class="recentten">
<h4 align=center>Front Page Posts</h4>
<ul>
%(recent)s
</ul>
</div>

</td>
</tr>

%(page_content)s

</table>
</body>
</html>
"""

row_template = """\
<tr>
<td height="1" class="arttitle">
<a name="%(anchor)s" /><h2>%(title)s</h2></td></tr>
<tr>
<td class="artbody" valign="top">
%(content)s
</td>
"""

_rightnav = None

def rightnav():
    """
    Return the right navigation bar.
    """

    global _rightnav
    
    if not _rightnav:
        f = file(webdir + "/rightnav.html", "r")
        _rightnav = f.read()
        f.close()

    return _rightnav

_recent = None

def recent():
    """
    Return links to the posts that are on the front pages.
    """

    global _recent

    if not _recent:
        _recent = ""    
        for bp in blog_posts(front_page_posts):
            _recent += '<li><a href="/index.shtml#%s">%s</a></li>\n' % ( bp.anchor, bp.title)

    return _recent
            
def format_page(subs, rows=None):
    """
    This formats a page using the given substitutions.
    
    @param subs: A dictionary of substitutions into the page
    template. The important field here is 'title', the title of the
    page.
    
    @param rows: A list of dictionaries, where each is interpreted as
    a row. The dictionaries are used as substitutions into the row
    template. Important fields in each row are 'title', the title of
    the row, and 'content', the contents of the row. If left as None,
    this is initialized to a list containing [ subs ]. This will be
    the case if there's a single entry on the page.

    @returns: The formatted page.
    """
    
    if rows is None:
        rows = [ subs ]

    page_content = ""

    for i, r in enumerate(rows):

        if 'anchor' not in r:
            r['anchor'] = 'a%d' % i

        page_content += row_template % r

    subs['rowcount'] = len(rows)
    subs['page_content'] = page_content
    subs['recent'] = recent()
    subs['rightnav'] = rightnav()

    return page_template % subs

class BlogPost(object):
    """
    This object represents a blog post, including all of the important
    information about the post. It doesn't store the formatted text of
    the post, but does allow it to be accessed.

    @ivar filename: The filename that the blog post is stored
    in. (Just the basename.)
    @ivar title: The title of the blog post.
    @ivar posted: The date on which the post was posted.
    @ivar lastmodified: The date on which the post was last modified.
    @ivar text: The text of the body of the blog post.
    """

    def __init__(self, filename):
        """
        This initializes a BlogPost object from a filename.
        
        @param filename: The basename of the filename the blog post is
        stored in.
        """
        
        self.filename = filename
        self.fullpath = blogdir + "/" + filename

        m = re.match(r"\d+-(\d+)", filename)
        self.posted = int(m.group(1))

        self.lastmodified = os.path.getmtime(self.fullpath)
        self.pagemodified = self.lastmodified

        f = file(self.fullpath)
        self.title = f.readline().strip()
        self.text = f.read()
        self.html = txt2html(self.text)
        f.close()

        self.anchor = str(self.posted)
        self.permalink = self.filename.replace(".blog.txt",
                                               ".shtml")
        self.comments = []

        cdir = "%s/%d" % ( commentdir, self.posted )

        if os.path.isdir(cdir):
            cfiles = os.listdir(cdir)
            cfiles.sort()

            for fn in cfiles:

                t = int(fn)

                if t > self.pagemodified:
                    self.pagemodified = t

                f = file(cdir + "/" + fn)
                f.readline()
                f.readline()
                self.comments.append((fn, f.read()))
                f.close()
            

    def subs(self):
        rv = {
            'dateline' : time.strftime("%A, %B %d, %Y",
                                       time.localtime(self.posted)),
            'rssdate' : time.strftime("%Y-%02m-%02dT%02H:%02M:%02SZ",
                                       time.localtime(self.posted)),
            'lastmodified' : time.strftime("%Y-%m-%d %H:%M",
                                           time.localtime(self.lastmodified)),
            'title' : self.title,
            'body' : self.html,
            'anchor' : self.anchor,
            'permalink' : self.permalink,
            'filename' : self.filename,
            'posted' : self.posted,
            }

        return rv

_blog_post_cache = {}

def blog_post(fn):
    """
    This is the same as the BlogPost constructor, except that it
    caches its argument.
    """
    
    if fn in _blog_post_cache:
        return _blog_post_cache[fn]

    rv = BlogPost(fn)
    _blog_post_cache[fn] = rv
    return rv

def blog_posts(count=None, suffix=".blog.txt"):
    """
    This returns a list of BlogPost objects, posts from the blog
    directory. The posts are generated from newest to oldest.
    
    @param count: If given, only generate the count newest entries.
    @param suffix: The suffix of blog post files.
    """
    
    entries = os.listdir(blogdir)

    entries = [ i for i in entries if i.endswith(suffix) ]

    entries.sort()
    entries.reverse()

    if count:
        entries = entries[:count]

    rv = []

    for i in entries:
        rv.append(blog_post(i))

    return rv



