py2html.py

'''\
Python to HTML.

commands:
    main            - Main function (normal use)
    demo            - Demo function (show off and test app)
    test            - Test function (development)

usage:
    module.dispatch({command}, args={arguments})    -- calling module from code

    $PYTHON module_name {command} {arguments}       -- executing module externally

Developer@Sonnack.com
April 2016
'''
####################################################################################################
from __future__ import print_function
from sys import argvexc_infostdoutstderr
from traceback import extract_tb
from os import path
from keyword import iskeyword
from htmlpage import htmlpage
from logger import loggerinfodebugtrace
####################################################################################################
Log = logger('app')

BasePath = r'C:\CJS\prj\Python\app'
HtmlPath = r'C:\CJS\www\root\pub\python'


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class TextSource (object):
    def __init__ (selftxt):
        self.src = txt
        self.cp = 0

    def rewind (self):
        self.cp = 0

    def get_ch (self):
        if self.cp < len(self.src):
            ch = self.src[self.cp]
            self.cp += 1
            return ch
        return None

    def unget (self):
        if 0 < self.cp:
            self.cp -= 1

##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class TextParser (object):
    SpclWords = ['self''other''this''that']
    SpcChr = '&nbsp;'
    TabChr = '&nbsp;' * 4

    def __init__ (self):
        self.src = None
        self.buf = ''

    def parse (selftxtfp):
        self.src = TextSource(txt)
        while True:
            ch = self.src.get_ch()
            if not ch:
                break
            if ch == ' ':
                fp.write(self.SpcChr)
                continue
            if ch == '\t':
                fp.write(self.TabChr)
                continue
            if ch == '<':
                fp.write('&lt;')
                continue
            if ch == '>':
                fp.write('&gt;')
                continue
            if ch == '&':
                fp.write('&amp;')
                continue
            if ch == ':':
                fp.write('<strong>:</strong>')
                continue
            if (ch == '"'or (ch == "'"):
                self._quoted_string(chfp)
                continue
            if ch == '#':
                self._line_comment(chfp)
                continue
            if ch.isdigit():
                self._number(chfp)
                continue
            if ch.isalpha() or (ch == '_'):
                self._keyword(chfp)
                continue
            fp.write(ch)

    def _keyword (selfchfp):
        self.buf = ch
        ch = self.src.get_ch()
        while ch and (ch.isalnum() or (ch == '_')):
            self.buf += ch
            ch = self.src.get_ch()
        # If a not-keyword char ended the number, unget it...
        if ch:
            self.src.unget()
        # See what kind of keyword it is...
        if self.buf in self.SpclWords:
            fp.write('<span class="Special">%s</span>' % self.buf)
            return
        if iskeyword(self.buf):
            fp.write('<span class="Keyword">%s</span>' % self.buf)
            return
        fp.write('<span class="Userdef">%s</span>' % self.buf)

    def _number (selfchfp):
        self.buf = ch
        ch = self.src.get_ch()
        while ch and ch.isdigit():
            self.buf += ch
            ch = self.src.get_ch()
        # If a not-number char ended the number, unget it...
        if ch:
            self.src.unget()
        fp.write('<span class="Number">%s</span>' % self.buf)

    def _line_comment (selfchfp):
        self.buf = ch
        ch = self.src.get_ch()
        while ch and (ch != '\n'):
            self.buf += ch
            ch = self.src.get_ch()
        # If a line-end ended this (as expected) put it back...
        if ch:
            self.src.unget()
        fp.write('<span class="Comment">%s</span>' % self.buf)

    def _quoted_string (selfchfp):
        ch1 = ch
        ch2 = self.src.get_ch()
        if ch2 == ch1:
            ch3 = self.src.get_ch()
            if ch3 == ch2:
                self._triple_quoted_text(chfp)
                return
            # Third char didn't match, return it...
            self.src.unget()
            # Emit the empty string...
            fp.write('<span class="String">%s%s</span>' % (ch1,ch2))
            return
        # Second char didn't match; non-empty string...
        self.buf = ch1
        self.buf += self._htmlize(ch2)
        ch = self.src.get_ch()
        while ch and (ch != ch1):
            self.buf += self._htmlize(ch)
            ch = self.src.get_ch()
        # If we exited on a quote, add it to the buffer...
        if ch:
            self.buf += ch
        fp.write('<span class="String">%s</span>' % self.buf)

    def _triple_quoted_text (selfchfp):
        self.buf = ch1 = ch * 3
        ch = self.src.get_ch()
        while ch:
            self.buf += self._htmlize(ch)
            if self.buf[-3:] == ch1:
                break
            ch = self.src.get_ch()
        fp.write('<span class="TripleQuote">%s</span>' % self.buf)

    def _htmlize (selfch):
        if ch == '<': return '&lt;'
        if ch == '>': return '&gt;'
        if ch == '&': return '&amp;'
        return ch


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class PythonPage (object):

    def __init__ (selfdata):
        self.data = data

    def writepage (selffp):
        fp.write('<pre style="background-image:url(/etc/bg_computer_paper.png)">')
        tp = TextParser()
        tp.parse(self.datafp)
        fp.write('</pre>\n')

##================================================================================================##
def python_to_html (py_filenamehtml_filename):
    Log.info('Python-2-HTML: py=%s' % py_filename)
    Log.info('Python-2-HTML: html=%s' % html_filename)

    # Python input file...
    bname = path.basename(py_filename)
    if not py_filename.endswith('.py'):
        Log.error('Invalid Extension: "%s"' % bname)
        raise RuntimeError('Not a Python source file: "%s"' % py_filename)

    # Read the source file...
    fp = open(py_filename'r')
    try:
        txt = fp.read()
        Log.info('read: %s (%d bytes)' % (bnamelen(txt)))
    except:
        raise
    finally:
        fp.close()

    # Generate HTML page...
    css = ['/basic.css''/pub/python/python.css']
    pw = PythonPage(txt)
    pg = htmlpage(bnameh2='Python Page'csspages=css)
    pg.writefile(html_filenamepw)
    #...
    Log.info('wrote: %s' % path.basename(html_filename))

    # All done...
    return 'Done!'


##================================================================================================##
def do_demo (*args):
    Log.info('demo: %s' % str(args))
    pname = path.join(BasePath'py2html.py')
    hname = path.join(HtmlPath'py2html.html')
    return python_to_html(pnamehname)
##================================================================================================##
def do_test (*args):
    Log.info('test: %s' % str(args))
    pname = args[0if 0 < len(argselse path.join(BasePath,'@.py')
    hname = args[1if 1 < len(argselse path.join(HtmlPath,'py2html.html')
    return python_to_html(pnamehname)
##================================================================================================##
def do_main (*args):
    Log.info('main: %s' % str(args))
    pname = args[0]
    hname = args[1if 1 < len(argselse path.join(HtmlName,'%s.html' % path.basename(pname))
    return python_to_html(pnamehname)
####################################################################################################
def dispatch (cmd, *args):
    Log.info('command: %s' % cmd)
    Log.info('arguments: %d' % len(args))
    if cmd == 'main': return do_main(*args)
    if cmd == 'test': return do_test(*args)
    if cmd == 'demo': return do_demo(*args)
    return 'Nothing to do!'
####################################################################################################
if __name__ == '__main__':
    print('autorun: %s' % argv[0])
    Log.start(path.join(BasePath,'py2html.log'))
    Log.level(info())
    cmd = argv[1if 1 < len(argvelse 'demo'
    etc = argv[2:if 2 < len(argvelse []
    try:
        obj = dispatch(cmd, *etc)
        print(obj)
        Log.info(obj)
    except:
        etypeevaluetb = exc_info()
        ts = extract_tb(tb)
        Log.error('%s: %s' % (etype.__name__,str(evalue)))
        for t in ts[-3:]:
            Log.error('[%d] %s  (%s)' % (t[1], t[2], t[0]))
            Log.error('    %s' % t[3])
        raise
    finally:
        Log.end()
####################################################################################################
'''eof'''