AstroJS/botanjs/service/jclassresv.py
2015-08-14 18:12:10 +08:00

336 lines
6.9 KiB
Python

import os
import re
import base64
import zlib
import hashlib
import xml.etree.ElementTree as ET
PY_SEP = os.path.sep
def wrapScope( src ):
return "(function(){" + src + "})();"
class Resolver:
resolved = None
bMap = None
parentMap = None
EX_PROP = "prop"
EX_FUNC = "method"
EX_CLASS = "class"
def __init__( self, classMap ):
self.classMap = classMap
self._reload()
def _reload( self ):
self.bMap = ET.parse( self.classMap )
# ElementTree does not support Element.find( ".." )
# Make a parent map to work around
self.parentMap = { c:p for p in self.bMap.iter() for c in p }
self.resolved = []
def resource( self, elem ):
if "src" in elem.attrib:
return elem.attrib[ "src" ]
parent = self.parentMap[ elem ]
if parent != None:
return self.resource( parent )
def resolve( self, c, classList ):
self.resolved = []
self.__resolve( c, classList )
def __resolve( self, c, classList ):
lista = list( "[@name=\"" + x + "\"]" for x in c.split(".") )
fx = "/".join( self.EX_CLASS + x for x in lista[:-1] )
it = lista[-1]
# Test if class
elem = self.bMap.find( ".//" + fx + self.EX_CLASS + it )
if elem == None:
if fx != '': fx += "/"
# Test if prop
elem = self.bMap.find( ".//" + fx + self.EX_PROP + it )
# test if func
if elem == None:
elem = self.bMap.find( ".//" + fx + self.EX_FUNC + it )
if elem == None:
raise LookupError( "No such class: " + c )
imports = self.parentMap[ elem ].findall( "import" )
else:
imports = elem.findall( "import" )
self.resolved.append( c )
for imp in imports:
if imp.text not in self.resolved:
self.__resolve( imp.text, classList )
classList.append([ c, elem ])
class BotanClassResolver:
R = ""
CR = None
classMap = ""
flagCompress = True
returnHash = False
resv = None
def __init__( self, jwork, BotanRoot, classMap, cacheRoot ):
self.JWork = jwork;
self.R = os.path.abspath( BotanRoot )
self.CR = os.path.abspath( cacheRoot )
os.makedirs( self.CR, 0o755, exist_ok = True )
if not os.path.exists( classMap ):
self.JWork.buildClassMap( self.R, classMap )
self.resv = Resolver( classMap )
def BotanFile( self, t ):
content = ""
with open( os.path.join( self.R, t ), "r" ) as f:
content = f.read()
return content
def BotanCache( self, t ):
content = ""
with open( t, "r" ) as f:
content = f.read()
return content
def cleanList( self, lista ):
olist = []
for i in lista:
if i not in olist:
olist.append( i )
return olist
def jsLookup( self, classList, classFiles ):
for c in classList:
src = self.resv.resource( c[1] )
if src == None:
raise LookupError( "Cannot find src file for: " + c[0] )
if src not in classFiles:
classFiles.append( src )
def cssLookup( self, jsList, cssList ):
for f in jsList:
possibleList = []
cssFile = os.path.splitext( f )[0] + ".css"
if cssFile not in possibleList:
possibleList.append( cssFile )
f = f.split( PY_SEP )
l = len( f )
for i in range( 1, l ):
cssFile = PY_SEP.join( x for x in f[:-i] ) + PY_SEP + "@_this.css"
if cssFile not in possibleList:
possibleList.append( cssFile )
possibleList.sort()
for f in possibleList:
f = f.replace( "@_this.css", "_this.css" )
if os.path.exists( os.path.join( self.R, f ) ):
cssList.append( f )
def getCache( self, fileList, cName, mode ):
if self.CR == None:
return None
md5 = hashlib.md5( bytearray( "".join( fileList ), "utf-8" ) ).hexdigest()
cName[0] = oFHash = md5 + "." + mode
cFHash = md5 + ".c." + mode
# Raw file
oFile = os.path.join( self.CR, oFHash )
# Compressed file
cFile = os.path.join( self.CR, cFHash )
dates = list(
os.path.getmtime( os.path.join( self.R, x ) )
if os.path.exists( os.path.join( self.R, x ) ) else -1
for x in fileList
)
# Root file date
dates.append( os.path.getmtime( os.path.join( self.R, "_this.js" ) ) );
if self.useCache( cFile, dates ) and self.flagCompress == True:
return cFHash if self.returnHash else self.BotanCache( cFile )
elif self.useCache( oFile, dates ):
self.JWork.saveCache.delay(
oFile
# Content is None to initiate a compression
, None
, mode
, os.path.join( self.R, "externs" )
)
return oFHash if self.returnHash else self.BotanCache( oFile )
def useCache( self, f, dList ):
if not os.path.exists( f ):
return False
t = os.path.getmtime( f )
for i in dList:
if t < i:
return False
return True
def compileJs( self, cList, xList ):
md5 = [ None ]
for x in xList:
cList.remove( x ) if x in cList else None
cacheFile = self.getCache( cList, md5, "js" )
if cacheFile != None:
return cacheFile;
# The root file
outputJs = self.BotanFile( "_this.js" )
for f in cList:
path = (
os.path.splitext( f )[0]
.replace( PY_SEP, "." )
.replace( "._this", "" )
)
struct = ";BotanJS.define( \"" + path + "\" );"
outputJs += struct + self.BotanFile( f )
outputJs = wrapScope( outputJs )
[ self.JWork.saveCache if self.returnHash else self.JWork.saveCache.delay ][0] (
os.path.join( self.CR, md5[0] )
, outputJs
, "js"
, os.path.join( self.R, "externs" )
)
if self.returnHash:
return md5[0]
return outputJs
def compileCss( self, cList, xList ):
cssIList = []
cssXList = []
self.cssLookup( cList, cssIList )
self.cssLookup( xList, cssXList )
cList = []
xList = cssXList
for x in cssIList:
cList.append( x ) if x not in xList else None
md5 = [ None ]
cacheFile = self.getCache( cList, md5, "css" )
if cacheFile != None:
return cacheFile;
outputCss = ""
for f in self.cleanList( cList ):
outputCss += self.BotanFile( f )
[ self.JWork.saveCache if self.returnHash else self.JWork.saveCache.delay ][0] (
os.path.join( self.CR, md5[0] ), outputCss, "css"
)
if self.returnHash:
return md5[0]
return outputCss
def getAPI( self, code, mode ):
self.flagCompress = True
# Should reload on debug mode only
self.resv._reload()
flag = mode[0]
requestAPIs = code
sp = "/"
if flag == "o":
mode = mode[1:]
elif flag == "r":
mode = mode[1:]
self.flagCompress = False
else:
self.returnHash = True
requestAPIs = (
# decode -> decompress -> split
zlib.decompress( base64.b64decode( code, None, True ) )
.decode( "utf-8" )
)
sp = ","
# strip malicious
requestAPIs = (
requestAPIs
.replace( "[^A-Za-z\.\*" + re.escape( sp ) + " ]", "" )
.split( sp )
)
imports = []
excludes = []
for apis in requestAPIs:
if apis == None: continue
classList = []
lookupList = imports
if apis[0] == "-":
apis = apis[1:]
lookupList = excludes
self.resv.resolve( apis, classList )
self.jsLookup( classList, lookupList )
if mode == "js":
return self.compileJs( imports, excludes )
elif mode == "css":
return self.compileCss( imports, excludes )
raise TypeError( "Invalid mode: " + js )