Initial commit

This commit is contained in:
2015-08-14 18:12:10 +08:00
parent f19f612fad
commit 4c93896df3
93 changed files with 4724 additions and 0 deletions

View File

View File

@@ -0,0 +1,335 @@
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 )

49
botanjs/service/jwork.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
import os
from celery import Celery
from celery.utils.log import get_task_logger
from botanjs.compressor.closure import Wrapper as ClosureWrapper
from botanjs.compressor.yui import Wrapper as YUIWrapper
from botanjs.classmap import ClassMap
app = Celery( "botanJWork" )
log = get_task_logger( __name__ )
if os.path.exists( "settings.ini" ):
from botanjs.config import Config
app.conf.update( BROKER_URL = Config["BotanJS"]["CeleryBroker"] )
class JWork:
@app.task()
def saveCache( location, content = None, mode = None, externs = "" ):
if content != None:
log.info( "Writing file(" + str( len( content ) ) + "): " + os.path.abspath( location ) )
with open( location, "w" ) as f:
f.write( content )
if mode == "js":
JWork.compressJs.delay( location, externs )
elif mode == "css":
JWork.compressCss.delay( location )
@app.task()
def compressJs( md5, externs ):
log.info( "Compress js: " + md5 )
w = ClosureWrapper()
w.scanExterns( externs )
w.compress( md5 )
@app.task()
def compressCss( md5 ):
log.info( "Compress css: " + md5 )
w = YUIWrapper()
w.compress( md5 )
@app.task()
def buildClassMap( src, location ):
log.info( "Building Class Map" )
c = ClassMap( src )
with open( location, "w" ) as f:
f.write( c.build() )

View File

@@ -0,0 +1,40 @@
<html>
<head>
<title>Botan JS - Test</title>
<script>
var debugEnv = true;
</script>
<link href="/ocss/Dandelion.CSSAnimations.MovieClip" rel="stylesheet" type="text/css" />
<script src="/ojs/Dandelion.CSSAnimations.MovieClip"></script>
<script>
function load ()
{
var CSSAnimations = BotanJS.import( "Dandelion.CSSAnimations.*" );
var mc = new CSSAnimations.MovieClip(
"http://file.astropenguin.net/blog/Gothic/__A.png"
// Row, Col
, 6 , 7
// wh
, 125 , 125
// Frame, start
, 42 , 0
);
// CSSAnimations.MouseOverMovie( mc );
document.body.appendChild( mc.stage );
mc.nextFrame();
mc.nextFrame();
mc.nextFrame();
mc.nextFrame();
mc.nextFrame();
mc.prevFrame();
};
</script>
<body onload="load()">
</body>
</html>

View File

@@ -0,0 +1,25 @@
<html>
<head>
<title>Botan JS - Test</title>
<script>
var debugEnv = true;
var _AstConf_ = { };
</script>
<link href="/ocss/System.Net.ClassLoader" rel="stylesheet" type="text/css" />
<script src="/ojs/System.Net.ClassLoader"></script>
<script>
var Loader = BotanJS.import( "System.Net.ClassLoader" );
var makeMessagebox = function()
{
var MessageBox = BotanJS.import( "Components.MessageBox" );
new MessageBox( "Title", "Message" ).show();
};
var ldr = new Loader( "/" );
ldr.load( "Components.MessageBox", makeMessagebox );
</script>
<body>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<html>
<head>
<title>Botan JS - Test</title>
<script>
var debugEnv = true;
var _AstConf_ = { };
</script>
<link href="/ocss/System.utils.IKey" rel="stylesheet" type="text/css" />
<script src="/ojs/System.utils.IKey"></script>
<body>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<html>
<head>
<title>Botan JS - List of Tests</title>
<style>
<!--
body {
font-size: 1.2em;
}
-->
</style>
<body>
<pre>
{% for d in data %}
<a href="/test/{{ d }}">{{ d }}</a>
<br>
{% endfor %}
</pre>
</body>
</html>

72
botanjs/service/webapi.py Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
from flask import Flask
from flask import Response
from flask import render_template
from botanjs.service.jclassresv import BotanClassResolver as JCResv
from botanjs.service.jwork import app as CeleryApp, JWork
import os
class WebAPI:
app = None
BRoot = None
BMap = None
BCache = None
def __init__( self, jsRoot = "../src", jsCache = "/tmp", brokerURL = None ):
self.test_templates = os.path.join(
os.path.dirname( os.path.realpath( __file__ ) )
, "templates"
, "test"
)
self.BRoot = os.path.abspath( jsRoot )
self.BCache = os.path.join( jsCache, "botanjs" )
self.BMap = os.path.join( self.BCache, "bmap.xml" )
if brokerURL != None:
CeleryApp.conf.update( BROKER_URL = brokerURL )
self.app = Flask( __name__, static_url_path = '', static_folder = self.BCache )
self.app.jinja_env.add_extension( "compressinja.html.HtmlCompressor" )
self.app.add_url_rule( "/test" , view_func = self.r_test_list )
self.app.add_url_rule( "/test/<string:test_file>" , view_func = self.r_test )
self.app.add_url_rule( "/<path:mode>/<path:code>" , view_func = self.api_request )
self.app.run( host="0.0.0.0" )
def r_test_list( self ):
for root, dirs, files in os.walk( self.test_templates ):
break
files.sort()
files = ( os.path.splitext( x )[0] for x in files )
return render_template( "test_list.html", data = files )
def r_test( self, test_file ):
return render_template( os.path.join( "test", test_file + ".html" ) )
def api_request( self, mode, code ):
try:
t = mode[1:]
if t == "js":
t = "application/javascript"
elif t == "css":
t = "text/css"
srvHandler = JCResv( JWork, self.BRoot, self.BMap, self.BCache )
return Response( srvHandler.getAPI( code, mode = mode ), mimetype = t )
except LookupError as e:
return str(e), 404
# except Exception as e:
# return str(e), 404
return "Invalid request", 404
if __name__ == "__main__":
WebAPI()