# ALMOST:  I think it works for single sessions, but once we
#  add a new posting, we will got some posts where all we know
#  about the post is what we read from the TDData.dat file, and
#  others where we have actual "post" objects from bzero.  The
#  latter will work, the former probably won't.  This is why we
#  need to get that first-line data filled in.
#
# Maintain and update a table of contents (TOC) for a bzero weblog.
# Put this source in /usr/local/lib/bzero/b0lib/updateTOC.py
# (or $BZERO_LIB/b0lib/updateTOC.py), and add these two lines
# into your index.py:
#  One, NEAR THE TOP (after the other "from b0lib import..." line):
#     from b0lib.updateTOC import updateTOC
#  Then at the end of the RenderSinglePost function, just after the
#   closing ")" that ends the ugly-long print statement:
#	updateTOC( post )
#
# To fire it up, clear out your ~/.bzero/(blogname)/rendered
#  directory, and then run "bzero (blogname) render".
# Thereafter, it ought to maintain itself, every time you cause
# bzero to perform a rendering.  I.e., updateTOC should be able
# to simply add the new entries without having to re-render
# everything.  If it does get screwed up, remove the TOC data
#  file (~/.bzero/(blogname)/TOCdata.dat) and the contents
#  of the rendered directory again, and re-run "bzero ... render".
#
# I wanted to use the pickle module, but it ran into weird
#  import problems that made it not worth using.
#
# Seguin, Texas claims to be the home of the world's largest pecan,
#  but there's a bigger one in Brunswick, Missouri.
# 

import re, os, os.path, time, sys
oj = os.path.join

Tdd_leaf = 'TDData.dat'
Tdd_prev = 'TDDPrev.dat'

TOC_out = 'site/TOC.txt'         # I write a .txt; bzero renders it
TOC_oprev = 'site/TOCprev.txt'

pathPat = re.compile(
    r'''[/\\:]([^/\\:]*)[/\\:]    # blog name
        (data[/\\:])              # grouped only to get its start index
        (\d\d\d\d)                # year
        [/\\:](\d*)[/\\:](\d*)    # month, day
        [/\\:](\d*).txt           # nr in day
    ''', re.VERBOSE )
URLPat = re.compile(
    r'''[/\\:]#(\d\d\d\d)      # year
               (\d\d)          # month
               (\d\d)          # day
               (\d)            # seq nr in day
    ''', re.VERBOSE )

AnchorPat = re.compile(
    r'''(\d\d\d\d)      # year
        (\d\d)          # month
        (\d\d)          # day
        (\d*)           # seq nr in day
    ''', re.VERBOSE )

URLHeadPat = re.compile(
    r'''(http://[^#]*)/\d\d\d\d/''', re.VERBOSE )

def dbg ( s ):
    # DISABLED for now.
    # stdout is redirected to the file being rendered,
    #  so my debugging output should go to stderr
    # print >>sys.stderr, s
    pass

class myPost:
    def __init__ ( self, bzPost ):
        self.bzPost = bzPost
        self.url = bzPost.GetUrl()
        anchorIx = self.url.index( '#' ) +1
        self.key = self.url[ anchorIx: ]
        self.title = bzPost[ 'title' ]
        self.path = bzPost.path

        mo = pathPat.search( bzPost.path )
        if not mo:
            raise 'Weird path %s, does not match path pattern' % self.path
        dbg( 'myPost gets match object ' + repr(mo) )
        g = mo.groups()
        dbg( 'mo groups' + repr(g) )
        self.blogname= mo.groups()[0]
        self.pathHead = self.path[ :mo.start(2) ]

    # post.day.number
    # day.month.year.number,
    # day.month.number,
    # day.number
    # okay then.  just do it.  half-right.

TDD_g = None
class TDD:  # TOC Data Dictionary class (singleton)
    def __init__ ( self, myp ):
        # Should be called only once per bzero session

        self.dPath = oj( myp.pathHead, Tdd_leaf )  # path to dict file
        dbg( 'TDD dPath ' + self.dPath )
        self.blogname = myp.blogname
        
        if os.path.exists( self.dPath ):
            fd = open( self.dPath, 'r' )
            self.dict = self.parse_dict(fd)
            fd.close()
        else:
            self.dict = {}
        self.prevPath = oj( myp.pathHead, Tdd_prev )

        # Set up the initial segment of the blog's URL
        mo = URLHeadPat.match( myp.url )
        self.URLHead = mo.group(1)
        dbg( 'URL(%s) -> URLHead (%s)\n' % (
            myp.url, self.URLHead ))

    def add ( self, mpost ):
        self.dict[ mpost.key ] = mpost

    #
    # Since pickle did not work for me, I had to invent a simple file
    #  format for the saved dictionary of posting titles.
    #  The first line has
    #   blogname@@some-damn-path@@some-URL
    #  The rest of the lines have
    #   anchor@@title
    #
    def parse_dict ( self, fd ):
        d = {}
        headline = fd.readline()[:-1]        
        lst = headline.split( '@@', 2 )
        self.blogname, self.URLHead = lst
        for ln in fd:
            anchor, title = ln.split( '@@', 1 )
            if title[-1] == '\n':
                title = title[:-1]
            d[ anchor ] = title
        return d

    def write_dict ( self ):
        backup( self.dPath, self.prevPath)
        Tddf = open( self.dPath, 'w' )
        Tddf.write( self.blogname + '@@' + self.URLHead + '\n' )
        keys = self.dict.keys()
        keys.sort()
        for k in keys:
            dbg( 'w_d k ' + repr(k) )
            val = self.dict[k]
            if type(val) == type(''):
                dbg( 'w_d s.d[k] ' + repr(self.dict[k]) )
                dbg( 'w_d title ' + repr(self.dict[k]) )
                Tddf.write( k + '@@' + self.dict[ k ] + '\n' )
            else:   # A post object
                dbg( 'w_d s.d[k] ' + repr(self.dict[k]) )
                dbg( 'w_d title ' + repr(self.dict[k].title) )
                Tddf.write( k + '@@' + self.dict[ k ].title + '\n' )
        Tddf.close()

def theTDD ( post ):
    global TDD_g
    if TDD_g is None:
        TDD_g = TDD( post )
    return TDD_g

def backup ( newName, oldName ):
    try:
        os.remove( oldName )
    except OSError, oe:
        pass              # It's ok for it not to exist
    try:
        os.rename( newName, oldName )
    except OSError, oe:
        pass              # It's ok for it not to exist

def updateTOC ( post ):
    ''' Given a post at rendering time, update the weblog's Table
        Of Contents.  My data file should be a simple pickled
        dictionary, indexed by the YYYYMMDDX label as a key,
        with the value being the title of the post.   But pickle
        let me down.  '''

    myP = myPost( post )
    dbg( 'updateTOC creating a posting ("myPost") object' )
    dbg( 'updateTOC:  myP.path ' + myP.path )
    TOC_data_dict = theTDD( myP )
    TOC_data_dict.add( myP )
    TOC_data_dict.write_dict()

    # Write the HTML version too
    TOC_outPath = oj( myP.pathHead, TOC_out )
    TOC_oprevP = oj( myP.pathHead, TOC_oprev )
    write_TOC( TOC_data_dict, TOC_outPath, TOC_oprevP )

months = [ 'January',   'February', 'March',    'April',
           'May',       'June',     'July',     'August',
           'September', 'October',  'November', 'December' ]
def monthName ( moNr ):
    return months[ int(moNr)-1 ]

def write_TOC ( Tdd, Tpath, Tprev ):
    # Back it up, if a previous version exists
    backup( Tpath, Tprev )
    TOCf = open( Tpath, 'w' )

    print >>TOCf, '%title ' + Tdd.blogname + ' TOC'

    nowTuple = time.localtime( )
    todayStr = '%4d-%02d-%02d' % ( nowTuple[0], nowTuple[1], nowTuple[2])

    print >>TOCf, '%date ' + todayStr
    print >>TOCf, '<a href="#today">TOC created ' + todayStr + '</a>'

    # For now, it's one big unordered (bullets) list
    print >>TOCf, ' <div class=TOCmonth>'

    anchKeys = Tdd.dict.keys()
    anchKeys.sort()
    day = -1
    month = -1
    
    lastK = anchKeys[-1]
    for aK in anchKeys:
        aPost = Tdd.dict[ aK ]
        if type(aPost) == type(''):
            # It's just a string; pick apart the key
            #  YYYY
            dbg( 'writeTOC, val is a string, key is ' + repr(aK) )
            mo = AnchorPat.search( aK )
            aPyr = mo.group(1)
            aPmo = mo.group(2).lstrip( '0' )
            aPday = mo.group(3).lstrip( '0' )
            url = '/'.join( [ Tdd.URLHead, aPyr, aPmo, aPday ] )
            title = aPost                 
        else:
            aPyr = repr(aPost.bzPost.day.month.year.number)
            aPmo = repr(aPost.bzPost.day.month.number)
            aPday = repr(aPost.bzPost.day.number)

            url = aPost.url
            # Work around a bzero bug (os.sep instead of '/' in URL)
            url = url.replace('\\','/',1)
            title = aPost.title

        if aPmo != month:
            if month != -1:
                print >>TOCf, '</div>'
                print >>TOCf, '\n%s, %s\n' % (
                    monthName(aPmo), aPyr )
                print >>TOCf, '<div class=TOCmonth>'

        TOCentry = '<a href="%s"> %s &#8212; %s</a>' % (
            url, aPday, title )
        if aK is lastK:
            # We might not actually have an entry "today", so I
            #  just give that name to the last entry.
            TOCentry = '<a name="today"> ' + TOCentry + ' </a>'

        print >>TOCf, TOCentry
        month= aPmo

    print >>TOCf, '</div>'


if __name__ == '__main__':

    class post:
        pass

    def test ( ):
        testPost = post()
        testPost.path = r'c:\dl\.bzero\zia\data\2003\3\16\1.txt'
        updateTOC( testPost )
        
    test( )
