• Facebook
  • Twitter
  • Reddit
  • StumbleUpon
  • Digg
  • email

#! /usr/bin/env python
# ______________________________________________________________________
"""Module ANOIki
 
Jonathan Riehl
 
$Id: ANOIki.py,v 1.8 2005/01/21 00:28:05 jriehl Exp $
"""
# ______________________________________________________________________
# Module imports
 
import re
import types
import string
import ANOIBaseSpace
import ANOITrie
import ANOITypes
import ANOITime
import ANOIUtil
 
# ______________________________________________________________________
# Module data
 
CHAR_MAX = 256
 
# FIXME: Parts of this regular expression are hacky; would like to stick as
# close to RFC 2396's absolute URI syntax as possible.
 
SCHEME_RE = "(([a-zA-Z][^\s:/?#]*):)"
PATH_RE = "(//([^\s/?#]*))([^\s?#\)\]]*)"
QUERY_RE = "(\\?([\s^#\)\]]*))?"
FRAGMENT_RE = "(#([^\s\)\]]*))?"
URL_RE_STR = "(%s%s%s%s)" % (SCHEME_RE, PATH_RE, QUERY_RE, FRAGMENT_RE)
urlReObj = re.compile(URL_RE_STR)
 
# This is also kinda hacky...
 
WIKI_RE_STR = "([A-Z][a-z]+([A-Z][a-z]+)+)"
wikiReObj = re.compile(WIKI_RE_STR)
 
UID_RE_STR = "(Uid[0-9]+)"
uidReObj = re.compile(UID_RE_STR)
 
RE_STR = "(%s)" % string.join([URL_RE_STR, WIKI_RE_STR, UID_RE_STR], "|")
reObj = re.compile(RE_STR)
 
# ______________________________________________________________________
# Class definitions
 
class ANOIkiFrontEnd (object):
    """Class ANOIkiFrontEnd
    """
    # ____________________________________________________________
    def __init__ (self, space, rootNSUid):
        """ANOITexter.__init__()
 
        Constructor for the ANOIkiFrontEnd class.
 
        Parameters:
 
        space - ANOI space object.
 
        rootNSUid - UID for the base trie.  This is the namespace of the type
        and time systems.
 
        Attributes created:
 
        space - This is the same as all other ANOI objects - a reference to
        the ANOI space that hosts the ANOIki.
 
        rootTrie - This is the root name space, used to name site wide
        attributes.
 
        namespace - This is the trie for article names.  By default this
        coincides with the root name space.
        """
        assert type(rootNSUid) == types.IntType
        self.space = space
        rootNS = ANOITrie.getNamespace(space, rootNSUid)
        articleNSUid = rootNS.getName("ANOIkiArticleRoot")
        if articleNSUid == ANOIBaseSpace.NIL:
            articleNSUid = self.space.getUid()
            rootNS.setName("ANOIkiArticleRoot", articleNSUid)
        self.namespace = ANOITrie.ANOITrie(space, articleNSUid, rootNS)
        urlNSUid = rootNS.getName("ANOIkiUrlRoot")
        if urlNSUid == ANOIBaseSpace.NIL:
            urlNSUid = self.space.getUid()
            rootNS.setName("ANOIkiUrlRoot", urlNSUid)
        self.urlNS = ANOITrie.ANOITrie(space, urlNSUid ,rootNS)
        self.root = self.namespace.root
        self.rootTrie = self.namespace.baseTrie
        self.typeSystem = ANOITypes.ANOITypeManager(space, rootNS)
        self.timeSystem = ANOITime.ANOITimeManager(space, rootNS, "TIME")
        self.createdTimeSystem = ANOITime.ANOITimeManager(space, rootNS,
                                                          "CREATED")
        # Get property keys
        self.titleAttr = self.typeSystem.getOrCreatePropertyKey("TITLE")
        self.authorAttr = self.typeSystem.getOrCreatePropertyKey("AUTHOR")
        # Get (or set) type stuff
        self.articleType = self.rootTrie.getName("ANOIkiArticleType")
        if self.articleType == ANOIBaseSpace.NIL:
            atomType = self.typeSystem.atomType
            aTypeUid = self.typeSystem.createType("ANOIkiArticleType",
                                                  {self.titleAttr : atomType,
                                                   self.authorAttr : None})
            self.articleType = aTypeUid
        self.placeHolderType = self.rootTrie.getName("ANOIkiPlaceHolderType")
        if self.placeHolderType == ANOIBaseSpace.NIL:
            phTypeUid = self.typeSystem.createType("ANOIkiPlaceHolderType")
            self.placeHolderType = phTypeUid
        self.externalLinkType = self.rootTrie.getName("ANOIkiExtLinkType")
        if self.externalLinkType == ANOIBaseSpace.NIL:
            extLinkTypeUid = self.typeSystem.createType("ANOIkiExtLinkType")
            self.externalLinkType = extLinkTypeUid
 
    # ____________________________________________________________
    def isArticle (self, uid):
        """ANOIkiFrontEnd.isArticle()
        """
        return self.typeSystem.getType(uid) == self.articleType
 
    # ____________________________________________________________
    def makeTitle (self, uid, title, authorName = None, authorUid = None):
        """ANOIkiFrontEnd.makeTitle()
        Force a UID to be of ANOIkiArticleType.
        """
        namedUid = self.namespace.getName(title)
        if namedUid == ANOIBaseSpace.NIL:
            self.namespace.setName(title, uid)
        elif namedUid != uid:
            raise ANOIBaseSpace.ANOIError, ("Title already maps to a UID "
                                            "(got %d, expected %d)." %
                                            (namedUid, uid))
        # Reassign type
        self.space.crossEquals(uid, self.typeSystem.typeAttr, self.articleType)
        self.space.crossEquals(uid, self.authorAttr, ANOIBaseSpace.NIL)
        self.space.crossEquals(uid, self.titleAttr, ANOIBaseSpace.NIL)
        self.setArticleAttributes(uid, title, authorName, authorUid)
 
    # ____________________________________________________________
    def addTitle (self, title, authorName = None, authorUid = None):
        """ANOIkiFrontEnd.addTitle()
 
        In some sense this has become the constructor for the
        ANOIkiArticleType.
        """
        retVal = ANOIBaseSpace.NIL
        if self.namespace.hasName(title):
            uid = self.namespace.getName(title)
            raise ANOIBaseSpace.ANOIError, ("Title already maps to a UID "
                                            "(%d)." % (uid,))
        else:
            # Article UID
            uid = self.typeSystem.createInstance(self.articleType)
            self.namespace.setName(title, uid)
            self.setArticleAttributes(uid, title, authorName, authorUid)
            # Good to go...
            retVal = uid
        return retVal
 
    # ____________________________________________________________
    def setArticleAttributes (self, uid, title, authorName = None,
                              authorUid = None):
        """ANOIkiFrontEnd.setArticleAttributes()
        """
        # Title UID
        titleUid = self.typeSystem.createInstance(self.typeSystem.atomType)
        self.space.setUidContent(titleUid, map(ord, list(title)))
        self.space.crossEquals(uid, self.titleAttr, titleUid)
        # Author UID
        if authorUid != None:
            self.space.crossEquals(uid, self.authorAttr, authorUid)
        elif authorName != None:
            if not self.namespace.hasName(authorName):
                authorUid = self.addTitle(authorName)
                self.space.crossEquals(authorUid, self.authorAttr,
                                       authorUid)
            else:
                authorUid = self.namespace.getName(authorName)
            self.space.crossEquals(uid, self.authorAttr, authorUid)
        # Times
        self.createdTimeSystem.timeStamp(uid)
        self.timeSystem.timeStamp(uid)
 
    # ____________________________________________________________
    def getTitleText (self, uid):
        """ANOIkiFrontEnd.getTitleText()
        Return the title associated with a UID, if it exists.  If no title
        mapping is found, returns None.
        """
        retVal = None
        if self.isArticle(uid):
            titleAtom = self.space.cross(uid, self.titleAttr)
            if titleAtom != ANOIBaseSpace.NIL:
                titleVec = self.space.getUidContent(titleAtom)
                retVal = string.join(map(chr, titleVec), "")
        return retVal
 
    # ____________________________________________________________
    def updateContent (self, uid, contentStr = None):
        """ANOIkiFrontEnd.updateContent()
        """
        contentVec = self.space.getUidContent(uid)
        if contentStr == None:
            contentStr = self.decompressArticle(uid)
        articleVec = self.compressArticle(contentStr)
        if articleVec != contentVec:
            self.space.setUidContent(uid, articleVec)
            self.timeSystem.timeStamp(uid)
 
    # ____________________________________________________________
    def getUrlUid (self, urlStr):
        """ANOIkiFrontEnd.getUrlUid()
        """
        retVal = self.urlNS.getName(urlStr)
        if retVal == ANOIBaseSpace.NIL:
            retVal = self.typeSystem.createInstance(self.externalLinkType)
            if retVal != ANOIBaseSpace.NIL:
                self.space.setUidContent(retVal, map(ord, list(urlStr)))
                self.urlNS.setName(urlStr, retVal)
        return retVal
 
    # ____________________________________________________________
    def getWikiUid (self, wikiStr):
        """ANOIkiFrontEnd.getWikiUid()
        """
        retVal = self.namespace.getName(wikiStr)
        if retVal == ANOIBaseSpace.NIL:
            retVal = self.typeSystem.createInstance(self.placeHolderType)
            if retVal != ANOIBaseSpace.NIL:
                self.space.setUidContent(retVal, map(ord, list(wikiStr)))
                self.namespace.setName(wikiStr, retVal)
        return retVal
 
    # ____________________________________________________________
    def compressArticle (self, content):
        """ANOIkiFrontEnd.compressArticle()
        """
        global urlReObj, wikiReObj, uidReObj, reObj
        # Suck out all the external references.
        contentList = list(content)
        matchIter = reObj.finditer(content)
        offset = 0
        for matchObj in matchIter:
            replaceUid = None
            matchStr = matchObj.groups()[0]
            if urlReObj.match(matchStr):
                replaceUid = self.getUrlUid(matchStr)
            elif wikiReObj.match(matchStr):
                replaceUid = self.getWikiUid(matchStr)
            else:
                try:
                    replaceUid = int(matchStr[3:])
                except:
                    replaceUid = None
            if replaceUid:
                start = matchObj.start()
                end = matchObj.end()
                contentList[start + offset: end + offset] = [replaceUid]
                offset -= (end - start) - 1
        def toUid (c):
            if type(c) == str:
                return ord(c)
            return c
        contentVec = map(toUid, contentList)
        return self.namespace.compressVector(contentVec)
 
    # ____________________________________________________________
    def decompressArticle (self, uid):
        """ANOIkiFrontEnd.decompressArticle()
 
        Outputs editable 7-bit ASCII text.
        """
        global CHAR_MAX
        retVec = []
        cross = self.space.cross
        contentVec = self.space.getUidContent(uid)
        for contentUid in contentVec:
            if contentUid < CHAR_MAX: # XXX - Dirty shameful hack.
                retVec.append(chr(contentUid))
            else:
                titleUid = cross(contentUid, self.titleAttr)
                if titleUid != ANOIBaseSpace.NIL:
                    titleVec = self.space.getUidContent(titleUid)
                    titleText = string.join(map(chr, titleVec), "")
                else:
                    titleText = "Uid%d" % contentUid
                retVec.append(titleText)
        return string.join(retVec, "")
 
    # ____________________________________________________________
    def htmlizeArticle (self, uid, baseURL):
        """ANOIkiFrontEnd.htmlizeArticle()
 
        Outputs HTML formatted article.
        """
        global CHAR_MAX
        retVec = []
        cross = self.space.cross
        contentVec = self.space.getUidContent(uid)
        for contentUid in contentVec:
            typeUid = self.typeSystem.getType(contentUid)
            text = None
            if typeUid == self.articleType:
                titleUid = cross(contentUid, self.titleAttr)
                if titleUid != ANOIBaseSpace.NIL:
                    titleVec = self.space.getUidContent(titleUid)
                    text = string.join(map(chr, titleVec), "")
                else:
                    text = "Uid%d" % contentUid
                retVec.append("<a href=\"%s?uid=%d\" class=\"article\">%s</a>"
                              % (baseURL, contentUid, text))
            elif typeUid == self.placeHolderType:
                holderVec = self.space.getUidContent(contentUid)
                holderStr = ANOIUtil.uidVecToStr(holderVec)
                # FIXME: The generated URL introduces additional coupling
                # between the anoi.cgi and the ANOIkiFrontEnd.
                # i.e. the CGI needs to support not just the uid parameter,
                # but the action parameter and corresponding value.
                retVec.append('<a href="%s?uid=%d;action=create" '
                              'class="placeholder">%s'
                              '<sup>?</sup></a>' % (baseURL, contentUid,
                                                    holderStr))
            elif typeUid == self.externalLinkType:
                urlVec = self.space.getUidContent(contentUid)
                urlStr = ANOIUtil.uidVecToStr(urlVec)
                retVec.append("<a href=\"%s\" class=\"external\">%s</a>" %
                              (urlStr, urlStr))
            else:
                if contentUid < CHAR_MAX:
                    contentChr = chr(contentUid)
                    if contentChr in (string.letters + string.digits):
                        text = contentChr
                    elif contentChr == '\n':
                        text = "<br/>"
                    elif contentChr == ' ':
                        text = " "
                    elif contentChr == '\t':
                        text = "    "
                    else:
                        text = "&#%d;" % contentUid
                else:
                    text = "Uid%d" % contentUid
                retVec.append("<a href=\"%s?uid=%d\" class=\"internal\">%s</a>"
                              % (baseURL, contentUid, text))
        return '<p>%s</p>' % string.join(retVec, "")
 
    # ____________________________________________________________
    def generateHtmlIndex (self, baseURL):
        """ANOIkiFrontEnd.generateHtmlIndex()
        """
        retVec = ["<ul>"]
        titleMap = self.namespace.getVectors()
        titles = titleMap.items()
        titles.sort()
        for (titleVec, refUid) in titles:
            title = string.join(map(chr, titleVec), "")
            if self.typeSystem.getType(refUid) == self.placeHolderType:
                pass
            else:
                retVec.append("<li><a href=\"%s?uid=%d\">%s</a></li>" %
                              (baseURL, refUid, title))
        retVec.append("</ul>")
        return string.join(retVec, "\r\n")
 
    # ____________________________________________________________
    def generateChanges (self, baseURL, maxCount = 20):
        """ANOIkiFrontEnd.generateChanges()
        """
        retList = []
        timeDim = self.timeSystem.timeDim
        neg = timeDim.negUid
        ref = timeDim.refUid
        count = 0
        listItemFmtStr = "<li>%s : <a href=\"%s?uid=%d\">%s</a></li>"
        crnt = self.space.cross(timeDim.uid, timeDim.posUid)
        while (crnt != ANOIBaseSpace.NIL) and (count < maxCount):
            referencedUid = self.space.cross(crnt, ref)
            if referencedUid != ANOIBaseSpace.NIL:
                titleUid = self.space.cross(referencedUid, self.titleAttr)
                if titleUid != ANOIBaseSpace.NIL:
                    titleVec = self.space.getUidContent(titleUid)
                    titleStr = ANOIUtil.uidVecToStr(titleVec)
                    timeVec = self.space.getUidContent(crnt)
                    timeStr = ANOIUtil.uidVecToStr(timeVec)
                    retList.append(listItemFmtStr % (timeStr, baseURL,
                                                     referencedUid, titleStr))
                    count += 1
            crnt = self.space.cross(crnt, neg)
        return "<ul>%s</ul>" % (string.join(retList, "\r\n"))
 
# ______________________________________________________________________
# End of ANOIki.py