Source

muyhomepage2 / muyhomepage2 / text / wikirst.py

# TODO cleanup

import re
import docutils
import docutils.core, docutils.writers, docutils.nodes

from muyhomepage2 import util


def format(text, enc='utf-8'):
    output = ReStructuredText(text).format()
    return output.decode(enc)



#  ~~~~~~~~~~~~~~~

class RestTranslator(docutils.nodes.GenericNodeVisitor):
	
    def __init__( self, document, parent_object=None ):
        docutils.nodes.GenericNodeVisitor.__init__( self, document )
        
        self.parent_object = parent_object
        self.content = []
        self.invisible = False
        self.linked = False

        self.freeze_text = False
        
        
    def unknown_visit( self, node ):
        print node
        
    def unknown_departure( self, node ):
        print node
        
    
    def default_visit( self, node ):
        if node.__class__ is not docutils.nodes.Text:
            return
        
        if self.invisible:
            return

        if self.alterText and not self.freeze_text:
            temp = self.alterText( self.prepContent( node.astext() ) )
            self.appendContent( temp )
            return

        self.appendContent( self.prepContent( node.astext() ) )
        return



    def prepContent( self, txt ):
        txt = txt.replace( '&', '&' )
        txt = txt.replace( '<', '&lt;' )
        txt = txt.replace( '>', '&gt;' )
        
        return txt


    def appendContent( self, txt ):
        self.content.append( txt )
    

    def default_departure( self, node ):
        pass


        
    
    
    def enterTag( self, tag, attrs ):
        #FIXME - does AttributeBuilder protect agains bad characters?
        self.content.append( '<%s%s>' % ( tag, str(attrs) )  )
        
        
    def exitTag( self, tag ):
        self.content.append( '</%s>' % tag )
    
    
    def astext( self ):
        return ''.join( self.content )
    
    
    def ezElement( cls, rst_elem, htm_elem, add_attrs=None ):

        if add_attrs is None:
            add_attrs = {}

        def visit_node( self_, node ):
            attrs = AttributeBuilder( node )
            attrs.update( add_attrs )
            self_.enterTag( htm_elem, attrs )

        def depart_node( self_, node ):
            self_.exitTag( htm_elem )
        
        setattr( cls, 'visit_%s' % rst_elem,  visit_node  )
        setattr( cls, 'depart_%s' % rst_elem, depart_node )


    ezElement = classmethod( ezElement )



    # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

    
    def visit_reference( self, node ):
        
        #attrs = self.genAttrs( node )
        #attrs['href'] = node.attributes.get( 'refuri', '' )
        attrs = AttributeBuilder( node )
        attrs.map( 'refuri', 'href' )

        self.linked = True
        self.enterTag( 'a', attrs )

    def depart_reference( self, node ):
        self.exitTag( 'a' )
        self.linked = False



    def visit_image( self, node ):
        #attrs = self.genAttrs( node, allow=('width','height','alt','align') )
        #
        #attrs[ 'src' ] = node.attributes.get( 'uri', '' )

        attrs = AttributeBuilder( node )
        attrs.allow( 'width', 'height', 'alt', 'align' )
        attrs.map( 'uri', 'src' )

        if 'scale' in node.attributes:
            scale = int( node.attributes['scale'] ) 
            
            if 'width' in attrs:
                attrs['width'] = ( int( attrs['width'] ) * scale ) // 100

            if 'height' in attrs:
                attrs['height'] = ( int( attrs['height'] ) * scale ) // 100
        

        self.enterTag( 'img', attrs )

    def depart_image( self, node ):
        self.exitTag( 'img' )
        

    def visit_paragraph( self, node ):
        self.enterTag( 'p', AttributeBuilder(node) )
    
    def depart_paragraph( self, node ):
        self.exitTag( 'p' )


    # reST comments must be _invisible_ to the output
    def visit_comment( self, node ):
        self.invisible = True

    def depart_comment( self, node ):
        self.invisible = False



RestTranslator.ezElement( 'document', 'div', {'class':'generated-text'} )

RestTranslator.ezElement( 'raw', 'pre' )
RestTranslator.ezElement( 'strong', 'strong' )
RestTranslator.ezElement( 'emphasis', 'em' )
RestTranslator.ezElement( 'superscript', 'super' )
RestTranslator.ezElement( 'subscript', 'sub' )
RestTranslator.ezElement( 'block_quote', 'blockquote' )
RestTranslator.ezElement( 'bullet_list', 'ul' )
RestTranslator.ezElement( 'enumerated_list', 'ol' )
RestTranslator.ezElement( 'title', 'h3' )
RestTranslator.ezElement( 'list_item', 'li' )
RestTranslator.ezElement( 'literal', 'tt', {'class':'literal'} )
RestTranslator.ezElement( 'literal_block', 'pre', {'class':'literal'} )
RestTranslator.ezElement( 'definition_list', 'dl' )
RestTranslator.ezElement( 'term', 'dt' )
RestTranslator.ezElement( 'definition', 'dd' )
RestTranslator.ezElement( 'classifier', 'span', {'class':'classifier'} )



class AttributeBuilder (object):

    DEF_MAP = {
        'class' : 'class',
    }

    def __init__( self, node, force=None ):
        self.node = node

        self._map = {}
        self._map.update( self.DEF_MAP )


    def keys( self ):
        return self._map.keys()

    def update( self, dct ):
        for key in dct:
            self[ key ] = dct[ key ]

    def values( self ):
        return list( self.itervalues() )

    def itervalues( self ):
        for key, value in self.iteritems():
            yield value

    def iteritems( self ):
        for key in self.keys():
            
            try:
                val = self[ key ]
            except KeyError:
                continue

            if val:
                yield key, val

    def items( self ):
        return list( self.iteritems() )

    
    def __repr__( self ):
        object.__repr__( self )

    def __str__( self ):
        pat = '%s="%s"'
        out = ' '.join( [pat % item for item in self.iteritems()] )

        if out:
            return ' ' + out

        return ''


    def __getitem__( self, key ):
        rtr = '_retrv_%s' % key

        try:
            return getattr( self, rtr )( key )
        except AttributeError:
            return self._retrv_Default( key )


    def __setitem__( self, key, value ):
        # a forced set makes it allowed
        self.allow( key )

        inj = '_inject_%s' % key 

        try:
            getattr( self, inj )( key, value )
        except:
            self._inject_Default( key, value )

    def allow( self, *args ):
        for arg in args:
            self._map[ arg ] = arg

    def map( self, internal, external ):
        self._map[ external ] = internal


    def _retrv_Default( self, key ):
        return self.node.attributes[ self._map[key] ]

    def _inject_Default( self, key, value ):
        self.node.attributes[ self._map[key] ] = value


    def _retrv_class( self, key ):
        return ' '.join( self.node.attributes['classes'] )

    def _inject_class( self, key, value ):
        return self.node.attributes['classes'].append( value )





class RestWriter (docutils.writers.Writer):

    def __init__( self, translator, parent_object=None ):
        docutils.writers.Writer.__init__( self )

        self.Translator = translator
        self.parent_object = parent_object

    def translate( self ):
        self.visitor = self.Translator( self.document, self.parent_object )
        self.document.walkabout( self.visitor )

        self.output = self.visitor.astext()



class ReStructuredText(object):
    def __init__(self, text, translator=None, writer=None):
        self.text = text
        self.translator = translator or RestTranslator
        self.translator.alterText = self.wikiMorph
        self.writer = writer or RestWriter
        self.findcmd = re.compile(self.pattern)

    def format(self):
        writer = self.writer(self.translator, self)
        return docutils.core.publish_string(self.text, writer=writer)

    pattern = r"(\[{2}|\{{2})(.*?)(\]{2}|\}{2})"

    def wikiMorph(self, text):
        text = self.findcmd.sub(self.handlecmd, text)
        return text

    def handlecmd(self, match):
        brStart, content, brEnd = match.groups()
        if brStart == '{{':
            return self.bracecmd(content)
        else:
            return self.bracketcmd(content)

    def bracketcmd(self, content):
        if '|' in content:
            target, text = content.split('|', 1)
        else:
            target = text = content
        target = './%s' % util.encodetitle(target)
        return '<a href="%s" class="wikilink">%s</a>' % (target, text)
        assert 1 == 2, content

    def bracecmd(self, content):
        return content


BRACKET_CMD = 'bracket'
BRACE_CMD   = 'brace'