Removed old impl

This commit is contained in:
2026-06-12 04:51:38 +08:00
parent 4fcd58b5ed
commit f532ada7c4
63 changed files with 717 additions and 1359 deletions
View File
-204
View File
@@ -1,204 +0,0 @@
import os, re, sys
from xml.dom import minidom
from collections import defaultdict
from botanjs.config import DEBUG
if DEBUG:
from botanjs.utils import checksum_r as checksum
else:
from botanjs.utils import checksum
RegEx_N = re.compile( r"""
.*
__namespace
\s*\(
\s*(['"])([^\1]+)\1
\s*\)
.*
""", re.X )
RegEx_I = re.compile( r"""
.*
__import
\s*\(
\s*(['"])([^\1]+)\1
\s*\)
.*
""", re.X )
RegEx_V = re.compile( r"""
.*
ns
\s*\[
\s*NS_INVOKE
\s*\]
\s*\(
\s*(['"])([^\1]+)\1
\s*\)
.*
""", re.X )
RegEx_E = re.compile( r"""
.*
ns
\s*\[
\s*NS_EXPORT
\s*\]
\s*\(
\s*EX_([A-Z_]+[A-Z])
\s*,
\s*(['"])([^\1]+)\2
\s*,
[^\)]+
\s*\)
.*
""", re.X )
def classMeta( cf ):
ns = ""
imps = list()
exps = list()
for line in open( cf, "r" ):
m = RegEx_N.match( line )
if m:
ns = m.group(2)
continue
m = RegEx_I.match( line )
if m:
imps.append( m.group(2) )
continue
m = RegEx_V.match( line )
if m:
imps.append( ns + "." + m.group(2) )
continue
m = RegEx_E.match( line )
if m:
exps.append( [ m.group(1), m.group(3) ] )
continue
return [ ns, imps, exps ]
def className( classFile ):
return ( os.path
.splitext( classFile )[0]
.replace( os.sep, "." )
.replace( "._this", "" )
.replace( "..BotanJS.", "" ) )
# __export types definition => nodeName
EX_CLASS = "class"
EX_FUNC = "method"
eDef = defaultdict( lambda: 'prop', { "CLASS": EX_CLASS, "FUNC": EX_FUNC } )
class ClassMap:
head = None
DOM = None
R = None
def __init__( self, BotanRoot ):
self.R = BotanRoot
self.DOM = minidom.parseString( "<BotanJS></BotanJS>" )
head = os.path.join( self.R, "_this.js" )
def getNode( self, name, t = EX_CLASS ):
paths = name.split( "." )
currentNode = self.DOM.firstChild
# Step down the path and create the path if necessary
for path in paths:
l = currentNode.childNodes
for i in l:
if i.getAttribute( "name" ) == path:
currentNode = i
break
if currentNode.getAttribute( "name" ) != path:
newNode = self.DOM.createElement( t )
newNode.setAttribute( "name", path )
currentNode.appendChild( newNode )
currentNode = newNode
return currentNode
def skipFile( self, cf ):
if os.path.splitext( cf )[1] == ".js":
if cf == self.head:
return True
return False
return True;
def drawMap( self, ns, ci, ce, cf, chksum ):
nsNode = self.getNode( ns )
# Source every:
# Since namespace may differ from file name
# ns.__export may export to a different level
# Source the exported val explicitly means
# exports only available when source file is imported
srcEvery = ( ns != className( cf ) )
cf = cf.replace( self.R, "" )
if not srcEvery:
nsNode.setAttribute( "src", cf )
nsNode.setAttribute( "js", chksum["js"] )
nsNode.setAttribute( "css", chksum["css"] )
for ex in ce:
_t = eDef[ ex[0] ]
cNode = self.getNode( ns + "." + ex[1], t = _t )
if srcEvery:
# The import is for the defined class
if _t == EX_CLASS:
for imp in ci:
impNode = self.DOM.createElement( "import" )
impNode.appendChild( self.DOM.createTextNode( imp ) )
cNode.appendChild( impNode )
cNode.setAttribute( "src", cf )
cNode.setAttribute( "js", chksum["js"] )
cNode.setAttribute( "css", chksum["css"] )
# the file dose not export classes
# Hence it import for itself
if not srcEvery:
for imp in ci:
impNode = self.DOM.createElement( "import" )
impNode.appendChild( self.DOM.createTextNode( imp ) )
nsNode.appendChild( impNode )
def build( self ):
for root, dirs, files in os.walk( self.R ):
if root == self.R:
dirs.remove("externs")
for name in files:
classFile = os.path.join( root, name )
if self.skipFile( classFile ):
continue
ns, ci, ce = classMeta( classFile )
chksum = {}
chksum[ "js" ] = checksum( classFile )
chksum[ "css" ] = checksum( classFile[:-2] + "css" )
classFile = classFile.replace( self.R + os.path.sep, "" )
self.drawMap( ns, ci, ce, classFile, chksum )
return self.DOM.toxml()
View File
-56
View File
@@ -1,56 +0,0 @@
#!/usr/bin/env python3
import os
from sys import platform
from tempfile import NamedTemporaryFile
from botanjs.config import Config as config
from botanjs.service.jwork import log
COMPILER = config[ "BotanJS" ][ "ClosureCompiler" ]
AVAILABLE = os.path.isfile( COMPILER )
COMPILER_OPTIONS = [
"--compilation_level ADVANCED_OPTIMIZATIONS"
, "--output_wrapper=\"(function(){%output%})();\""
]
class Wrapper:
C = None
# externs
E = ""
def __init__( self ):
self.C = "java -jar -Xmx64M "+ COMPILER + " " + " ".join( COMPILER_OPTIONS )
def scanExterns( self, sdir ):
for root, dirs, files in os.walk( sdir ):
# Split file extensions
files = list( os.path.splitext( x ) for x in files )
files.sort()
for f in files:
files.remove( f ) if f[1] != ".js" else None
self.E = " --externs " + " --externs ".join(
os.path.join( root, x )
# join back extensions
for x in list( "".join( x ) for x in files )
)
break
def compress( self, loc ):
if not AVAILABLE:
log.error( "Compiler not found" )
return
content = ""
with open( loc, "rb" ) as f:
content = f.read()
with NamedTemporaryFile( delete = ( not platform == "win32" ) ) as f:
f.write( content[12:-5] )
os.system( self.C + self.E + " --js " + f.name + " --js_output_file " + loc[:-3] + ".c.js" )
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env python3
import os
from sys import platform
from botanjs.config import Config as config
COMPILER = config[ "BotanJS" ][ "YuiCompressor" ]
if not os.path.isfile( COMPILER ):
raise Exception( "Compiler not found" )
COMPILER_OPTIONS = [
"--type css"
]
class Wrapper:
C = None
def __init__( self ):
self.C = "java -jar " + COMPILER + " " + " ".join( COMPILER_OPTIONS )
def compress( self, loc ):
if platform == "win32":
loc = loc.replace( "C:", "" ).replace( "\\\\", "/" )
os.system( self.C + " " + loc + " -o " + loc[:-4] + ".c.css" )
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env python3
import configparser, os
Config = configparser.ConfigParser( interpolation = configparser.ExtendedInterpolation() )
Config.read( "settings.ini" )
DEBUG = os.getenv( "DEBUG" )
if DEBUG is None:
DEBUG = Config.getboolean( "Env", "Debug" )
else:
Config[ "Env" ][ "Debug" ] = str( DEBUG == "1" )
REDIS_CONN = os.getenv( "REDIS_CONN" )
if not REDIS_CONN is None:
Config[ "Redis" ][ "ConnStr" ] = REDIS_CONN
-34
View File
@@ -1,34 +0,0 @@
#!/usr/bin/env python3
class log:
@staticmethod
def info( *args ):
print( *args )
@staticmethod
def error( *args ):
print( *args )
class dummyTask( object ):
Func = None
def delay( self, *args ):
self.Func( *args )
def __init__( self, *args ):
self.Func = args[0]
def __call__( self, *args ):
pass
class dummyConf:
def update( self, broker_url = None ):
pass
class app:
conf = dummyConf()
def task():
return dummyTask
View File
-368
View File
@@ -1,368 +0,0 @@
import os, re, base64, zlib, hashlib, binascii
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"
_rLookup = None
def __init__( self, classMap ):
self.classMap = classMap
self._rLookup = {}
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:
key = elem.attrib[ "src" ]
if not key in self._rLookup:
self._rLookup[ key ] = {
"src": elem.attrib[ "src" ]
, "js": elem.attrib[ "js" ]
, "css": elem.attrib[ "css" ]
}
return self._rLookup[ key ]
parent = self.parentMap[ elem ]
if parent is not None:
return self.resource( parent )
def locate( self, key ):
return self._rLookup.get( key )
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] )
# resolve wildcard A.B.*
if c[-2:] == ".*":
elem = self.bMap.findall( ".//" + fx + self.EX_CLASS )
if elem == None:
raise LookupError( "Namespace does not exists or contains no classes: " + c )
c = c[0:-1]
for cl in elem:
cl = c + cl.attrib[ "name" ]
if cl not in self.resolved:
self.__resolve( cl, classList )
return
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
oModeIfSizelt = 50 * 1024
returnHash = False
returnDynamic = 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, srcFile, fileHash, hashContentDynamic ):
for _ in [0]:
if hashContentDynamic and os.path.getsize( srcFile ) < self.oModeIfSizelt:
break
if self.returnHash:
return fileHash
with open( srcFile, "r" ) as f:
return f.read()
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, classList, cssList ):
for cdef in classList:
f = cdef[ "src" ]
cssFile = os.path.splitext( f )[0] + ".css"
cssGroup = []
if not cdef[ "css" ] == "1":
cssGroup.append( cdef )
f = f.split( PY_SEP )
l = len( f )
for i in range( 1, l ):
key = PY_SEP.join( x for x in f[:-i] ) + PY_SEP + "_this.js"
_def = self.resv.locate( key )
if _def and not _def[ "css" ] == "1":
cssGroup.append( _def )
cssGroup.sort( key = lambda x : x[ "src" ] )
cssList.extend( cssGroup )
def getCache( self, fileList, cName, mode ):
if self.CR == None:
return None
md5 = hashlib.md5( bytearray( "|".join( x[mode] for x in 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 )
cCached = self.useCache( cFile )
if self.flagCompress and cCached:
return self.BotanCache( cFile, cFHash, self.returnDynamic )
if self.useCache( oFile ):
if not cCached:
self.JWork.saveCache(
oFile
# Content is None to initiate a compression
, None
, mode
, os.path.join( self.R, "externs" )
)
return self.BotanCache( oFile, oFHash, False )
return None
def useCache( self, f ):
return os.path.exists( f )
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 is not None:
return cacheFile;
# The root file
outputJs = self.BotanFile( "_this.js" )
for f in cList:
f = f[ "src" ]
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(
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 is not None:
return cacheFile;
struct = "/* @ */"
outputCss = struct + self.BotanFile( "_this.css" )
for f in self.cleanList( cList ):
outputCss += self.BotanFile( f["src"][:-2] + "css" )
self.JWork.saveCache( 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
# Return compressed contents if possible
# otherwise return raw contents
if flag == "o":
mode = mode[1:]
# Return raw contents only
elif flag == "r":
mode = mode[1:]
self.flagCompress = False
# Return hashed filenames only
elif flag == "h":
mode = mode[1:]
self.returnHash = True
# Return hashed filenames if content is larger than self.oModeIfSizelt bytes
# otherwise act as "o" mode
else:
self.returnHash = True
self.returnDynamic = True
try:
requestAPIs = (
# decode -> decompress -> split
zlib.decompress( base64.b64decode( code, None, True ) )
.decode( "utf-8" )
)
sp = ","
except binascii.Error:
sp = "/"
# strip malicious
requestAPIs = (
requestAPIs
.replace( "[^A-Za-z\.\*" + re.escape( sp ) + " ]", "" )
.split( sp )
)
imports = []
excludes = []
for apis in requestAPIs:
if not apis:
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 )
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env python3
import os
from botanjs.classmap import ClassMap
if os.getenv( "UNIT_TEST" ) == "1":
CeleryExists = False
else:
CeleryExists = True
try:
from celery import Celery
except ImportError:
CeleryExists = False
if CeleryExists:
from celery.utils.log import get_task_logger
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"] )
else:
from botanjs.dummy import app
from botanjs.dummy import log
class JWork:
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( location )
@app.task()
def compressJs( md5, externs ):
from botanjs.compressor.closure import Wrapper as ClosureWrapper
log.info( "Compress js: " + md5 )
w = ClosureWrapper()
w.scanExterns( externs )
w.compress( md5 )
@app.task()
def compressCss( md5 ):
from botanjs.compressor.yui import Wrapper as YUIWrapper
log.info( "Compress css: " + md5 )
w = YUIWrapper()
w.compress( md5 )
@app.task()
def buildClassMap( src, location ):
log.info( "Building Class Map" )
c = ClassMap( src )
os.makedirs( os.path.dirname( location ), exist_ok = True )
with open( location, "w" ) as f:
f.write( c.build() )
-66
View File
@@ -1,66 +0,0 @@
from flask import Flask
from flask import Response
from flask import render_template
from flask import request
from botanjs.service.jclassresv import BotanClassResolver as JCResv
from botanjs.service.jwork import app as CeleryApp, JWork
from botanjs.config import Config
import os
class WebAPI:
app = None
BRoot = None
BMap = None
BCache = None
def __init__( self, jsRoot = "../src", jsCache = "/tmp", brokerURL = None ):
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 = "/cache/botanjs", static_folder = self.BCache )
self.app.jinja_env.add_extension( "compressinja.html.HtmlCompressor" )
self.app.add_url_rule( "/" , view_func = self.index )
self.app.add_url_rule( "/<mode>/" , view_func = lambda mode: self.api_request( mode, "zpayload" ) )
self.app.add_url_rule( "/<mode>/<path:code>" , view_func = self.api_request )
def run( self, *args, **kwargs ):
JWork.buildClassMap( self.BRoot, self.BMap )
return self.app.run( *args, **kwargs )
def index( self ):
return "Hello, this is the BotanJS Service API.", 200
def api_request( self, mode, code ):
if mode == "rebuild":
JWork.buildClassMap.delay( self.BRoot, self.BMap )
return "OK", 200
if code == "zpayload":
code = request.args.get( "p" )
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 Exception as e:
if self.app.config[ "DEBUG" ]:
raise
return str(e), 404
return "Invalid request", 404
-14
View File
@@ -1,14 +0,0 @@
from functools import lru_cache
from zlib import adler32 as _HashFunc
HashFunc = lambda v: hex( _HashFunc( v ) )[2:]
@lru_cache( maxsize = 1024 )
def checksum( file_path ):
return checksum_r( file_path )
def checksum_r( file_path ):
try:
with open( file_path, "rb" ) as f:
return HashFunc( f.read() )
except FileNotFoundError:
return HashFunc( b"" )
@@ -9,6 +9,7 @@
// Character cloud creates a cloud of character with randomized properties // Character cloud creates a cloud of character with randomized properties
var create = function( ch, char_class, size, cloudRange, charSize ) var create = function( ch, char_class, size, cloudRange, charSize )
{ {
var marginLeft;
var cloudMap = Dand.wrapc( "characterCloud" ) var cloudMap = Dand.wrapc( "characterCloud" )
, charElmt , charElmt
, rx, ry, rs , rx, ry, rs
+1 -1
View File
@@ -18,7 +18,7 @@
|| window.mozRequestAnimationFrame || window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame || window.oRequestAnimationFrame
|| window.msRequestAnimationFrame || window.msRequestAnimationFrame
|| function( f ) { window.setTimeout( draw1, 1000 / 60 ); } || function( f ) { window.setTimeout( f, 1000 / 60 ); }
); );
var cssSlide = function( element, slide_index, distance ) var cssSlide = function( element, slide_index, distance )
+4 -3
View File
@@ -58,7 +58,7 @@
, led = null , led = null
, ticking = function () { , ticking = function () {
if( debugEnv ) if( window[ "debugEnv" ] )
{ {
time_txt.innerHTML = ( sTick.count - cycle ) + " cps, Sampling " + sampling + "ms"; time_txt.innerHTML = ( sTick.count - cycle ) + " cps, Sampling " + sampling + "ms";
cycle = sTick.count; cycle = sTick.count;
@@ -130,12 +130,13 @@
var autoHide = function () { this.style.top = ""; }.bind(stage); var autoHide = function () { this.style.top = ""; }.bind(stage);
Cycle.perma('gTicker' + Perf.uuid, ticking, sampling); Cycle.perma('gTicker' + Perf.uuid, ticking, sampling);
Cycle.perma('gTicker' + Perf.uuid, autoHide, 3000); Cycle.perma('gTicker' + Perf.uuid, autoHide, 3000);
debugEnv = true; window[ "debugEnv" ] = true;
ticking(); ticking();
var f9Binding = function ( e ) var f9Binding = function ( e )
{ {
var code;
e = e || window.event; e = e || window.event;
if ( e.keyCode ) code = e.keyCode; if ( e.keyCode ) code = e.keyCode;
else if ( e.which ) code = e.which; else if ( e.which ) code = e.which;
@@ -167,7 +168,7 @@
// This will output the debug info // This will output the debug info
if( window["debug_info"] ) if( window["debug_info"] )
{ {
debug.Info( objTreeView( debug_info, 0, "[Server] " ) ); debug.Info( objTreeView( window[ "debug_info" ], 0, "[Server] " ) );
} }
}; };
+2 -2
View File
@@ -66,7 +66,7 @@ var BotanEvent = function( name, data )
}; };
/** @constructor /** @constructor
* @extends EventTarget * @implements {EventTarget}
**/ **/
var EventDispatcher = function() { var EventDispatcher = function() {
var events = {}; var events = {};
@@ -206,7 +206,7 @@ __namespace = __namespace || function( ns )
target.__TRIGGERS = []; target.__TRIGGERS = [];
nsObj = new NamespaceObj; var nsObj = new NamespaceObj;
nsObj[ NS_EXPORT ] = function( type, name, obj ) nsObj[ NS_EXPORT ] = function( type, name, obj )
{ {
if( this.t[ name ] ) return; if( this.t[ name ] ) return;
@@ -1,8 +1,8 @@
/** @constructor */ /** @constructor */
Astro.Blog.AstroEdit.SmartInput.ICandidateAction = function(){}; Astro.Blog.AstroEdit.SmartInput.ICandidateAction = function(){};
/** @type {function} */ /** @type {function(function(): void): void} */
Astro.Blog.AstroEdit.SmartInput.ICandidateAction.GetCandidates; Astro.Blog.AstroEdit.SmartInput.ICandidateAction.GetCandidates;
/** @type {function} */ /** @type {function(string): void} */
Astro.Blog.AstroEdit.SmartInput.ICandidateAction.Process; Astro.Blog.AstroEdit.SmartInput.ICandidateAction.Process;
/** @type {function} */ /** @type {function(Astro.Blog.AstroEdit.SmartInput.ICandidateAction, Event): boolean} */
Astro.Blog.AstroEdit.SmartInput.ICandidateAction.Retreat; Astro.Blog.AstroEdit.SmartInput.ICandidateAction.Retreat;
+5 -5
View File
@@ -12,13 +12,13 @@ Astro.utils.Date.smstamp;
/** @type {Function}*/ /** @type {Function}*/
Astro.utils.Date.chinese; Astro.utils.Date.chinese;
/** @type {constant}*/ /** @const*/
Astro.utils.Date.MONTH; Astro.utils.Date.MONTH;
/** @type {constant}*/ /** @const*/
Astro.utils.Date.MONTH_ABBR; Astro.utils.Date.MONTH_ABBR;
/** @type {constant}*/ /** @const*/
Astro.utils.Date.DAY; Astro.utils.Date.DAY;
/** @type {constant}*/ /** @const*/
Astro.utils.Date.DAY_ABBR; Astro.utils.Date.DAY_ABBR;
/** @type {constant}*/ /** @const*/
Astro.utils.Date.CAP_MONTHS; Astro.utils.Date.CAP_MONTHS;
+9 -9
View File
@@ -1,31 +1,31 @@
/** @constructor */ /** @constructor */
var Dandelion = function (){} var Dandelion = function (){}
/** @type {Function} */ /** @type {function(...?): HTMLElement} */
Dandelion.wrap; Dandelion.wrap;
/** @type {Function} */ /** @type {function(...?): HTMLElement} */
Dandelion.wrapc; Dandelion.wrapc;
/** @type {Function} */ /** @type {function(...?): HTMLElement} */
Dandelion.wrape; Dandelion.wrape;
/** @type {Function} */ /** @type {function(...?): HTMLElement} */
Dandelion.wrapne; Dandelion.wrapne;
/** @type {Function} */ /** @type {function(...?): HTMLElement} */
Dandelion.wrapna; Dandelion.wrapna;
/** @type {Function} */ /** @type {function(string): HTMLElement} */
Dandelion.textNode; Dandelion.textNode;
/** @type {Function} */ /** @type {function(string): HTMLElement} */
Dandelion.bubbleUp; Dandelion.bubbleUp;
/** @type {Function} */ /** @type {function(HTMLElement, function(HTMLElement): boolean): void} */
Dandelion.chainUpApply; Dandelion.chainUpApply;
/** @type {Function} */ /** @type {function(string, boolean=): (HTMLElement|Dandelion.IDOMElement)} */
Dandelion.id; Dandelion.id;
/** @type {Function} */ /** @type {Function} */
@@ -1,42 +1,60 @@
/** @constructor */ /** @constructor */
Libraries.SyntaxHighlighter = function() {}; Libraries.SyntaxHighlighter = function() {};
/** @type {Function} */ /** @type {function(...?): ?} */
Libraries.SyntaxHighlighter.all; Libraries.SyntaxHighlighter.all;
/** @type {Object} */
/** @type {!Object<string, *>} */
Libraries.SyntaxHighlighter.defaults; Libraries.SyntaxHighlighter.defaults;
/** @type {Function} */
/** @type {function(...?): ?} */
Libraries.SyntaxHighlighter.highlight; Libraries.SyntaxHighlighter.highlight;
/** @type {Object} */
/** @type {!Object<string, *>} */
Libraries.SyntaxHighlighter.Highlighter; Libraries.SyntaxHighlighter.Highlighter;
/** @type {Object} */ /** @type {!Object<string, *>} */
Libraries.SyntaxHighlighter.brushes; Libraries.SyntaxHighlighter.brushes;
/** @type {Object} */ /** @type {!Object<string, !RegExp>} */
Libraries.SyntaxHighlighter.regexLib; Libraries.SyntaxHighlighter.regexLib;
/** @constructor */
/** @const */
var SyntaxHighlighter = {}; var SyntaxHighlighter = {};
/** @type {RegExp} */
SyntaxHighlighter.regexLib.aspScriptTags /** @type {!Object<string, !RegExp>} */
/** @type {RegExp} */ SyntaxHighlighter.regexLib;
SyntaxHighlighter.regexLib.doubleQuotedString
/** @type {RegExp} */ /** @type {!RegExp} */
SyntaxHighlighter.regexLib.multiLineCComments SyntaxHighlighter.regexLib.aspScriptTags;
/** @type {RegExp} */
SyntaxHighlighter.regexLib.multiLineDoubleQuotedString /** @type {!RegExp} */
/** @type {RegExp} */ SyntaxHighlighter.regexLib.doubleQuotedString;
SyntaxHighlighter.regexLib.multiLineSingleQuotedString
/** @type {RegExp} */ /** @type {!RegExp} */
SyntaxHighlighter.regexLib.phpScriptTags SyntaxHighlighter.regexLib.multiLineCComments;
/** @type {RegExp} */
SyntaxHighlighter.regexLib.scriptScriptTags /** @type {!RegExp} */
/** @type {RegExp} */ SyntaxHighlighter.regexLib.multiLineDoubleQuotedString;
SyntaxHighlighter.regexLib.singleLineCComments
/** @type {RegExp} */ /** @type {!RegExp} */
SyntaxHighlighter.regexLib.singleLinePerlComments SyntaxHighlighter.regexLib.multiLineSingleQuotedString;
/** @type {RegExp} */
SyntaxHighlighter.regexLib.singleQuotedString /** @type {!RegExp} */
/** @type {RegExp} */ SyntaxHighlighter.regexLib.phpScriptTags;
SyntaxHighlighter.regexLib.xmlComments
/** @type {!RegExp} */
SyntaxHighlighter.regexLib.scriptScriptTags;
/** @type {!RegExp} */
SyntaxHighlighter.regexLib.singleLineCComments;
/** @type {!RegExp} */
SyntaxHighlighter.regexLib.singleLinePerlComments;
/** @type {!RegExp} */
SyntaxHighlighter.regexLib.singleQuotedString;
/** @type {!RegExp} */
SyntaxHighlighter.regexLib.xmlComments;
@@ -1,7 +1,7 @@
/** @type {constructor} */ /** @constructor */
System.Compression.Zlib = function(){}; System.Compression.Zlib = function(){};
/** @type {constructor} */ /** @constructor */
System.Compression.Zlib.Deflate = function(){}; System.Compression.Zlib.Deflate = function(){};
/** @type {Function} */ /** @type {Function} */
+3 -3
View File
@@ -8,9 +8,9 @@ System.Log.registerHandler;
/** @type {Function} */ /** @type {Function} */
System.Log.removeHandler; System.Log.removeHandler;
/** @type {const} */ /** @const */
System.Log.ERROR; System.Log.ERROR;
/** @type {const} */ /** @const */
System.Log.INFO; System.Log.INFO;
/** @type {const} */ /** @const */
System.Log.SYSTEM; System.Log.SYSTEM;
+2 -2
View File
@@ -1,8 +1,8 @@
/** @type {Object} */ /** @type {!Object<string, *>} */
_AstConf_.AstroEdit = {}; _AstConf_.AstroEdit = {};
/** @type {String} */ /** @type {String} */
_AstConf_.AstroEdit.article_id; _AstConf_.AstroEdit.article_id;
/** @type {object} */ /** @type {!Object<string, *>} */
_AstConf_.AstroEdit.paths = {}; _AstConf_.AstroEdit.paths = {};
/** @type {string} */ /** @type {string} */
_AstConf_.AstroEdit.paths.set_article; _AstConf_.AstroEdit.paths.set_article;
+3 -2
View File
@@ -20,17 +20,18 @@ FROM eclipse-temurin:21-jre
WORKDIR /app WORKDIR /app
ARG JS_SRC_DIR
ARG JAVA_SRC_DIR ARG JAVA_SRC_DIR
ARG CLOSURE_NAME ARG CLOSURE_NAME
RUN useradd -r -u 10001 closure RUN useradd -r -u 10001 closure
COPY --from=build /src/target/${CLOSURE_NAME}-0.1.0.jar /app/runtime.jar COPY --from=build /src/target/${CLOSURE_NAME}-0.1.0.jar /app/runtime.jar
COPY $JAVA_SRC_DIR/example ./example COPY $JS_SRC_DIR ./src
USER closure USER closure
ENV CLOSURED_ROOT=/work ENV CLOSURED_ROOT=/app/src
ENV CLOSURED_PORT=8080 ENV CLOSURED_PORT=8080
ENV CLOSURED_WORKERS=2 ENV CLOSURED_WORKERS=2
@@ -27,6 +27,8 @@ import java.util.Map;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public final class Main { public final class Main {
private record InlineSource(String name, String source) {}
private static final ObjectMapper JSON = new ObjectMapper(); private static final ObjectMapper JSON = new ObjectMapper();
private static final Path ROOT = Path.of(System.getenv().getOrDefault("CLOSURED_ROOT", ".")).toAbsolutePath().normalize(); private static final Path ROOT = Path.of(System.getenv().getOrDefault("CLOSURED_ROOT", ".")).toAbsolutePath().normalize();
@@ -92,11 +94,14 @@ public final class Main {
List<SourceFile> externs = new ArrayList<>(); List<SourceFile> externs = new ArrayList<>();
externs.addAll(CommandLineRunner.getBuiltinExterns(options.getEnvironment())); externs.addAll(CommandLineRunner.getBuiltinExterns(options.getEnvironment()));
externs.addAll(readFiles(req, "externs")); externs.addAll(readFiles(req, "externs"));
externs.addAll(readInlineSources(req, "externSources"));
List<SourceFile> inputs = readFiles(req, "js"); List<SourceFile> inputs = new ArrayList<>();
inputs.addAll(readFiles(req, "js"));
inputs.addAll(readInlineSources(req, "jsSources"));
if (inputs.isEmpty()) { if (inputs.isEmpty()) {
throw new BadRequest("request must include at least one js file"); throw new BadRequest("request must include at least one js file or js source");
} }
com.google.javascript.jscomp.Compiler compiler = com.google.javascript.jscomp.Compiler compiler =
@@ -154,6 +159,52 @@ public final class Main {
return out; return out;
} }
private static List<SourceFile> readInlineSources(JsonNode req, String field) throws BadRequest {
JsonNode arr = req.get(field);
List<SourceFile> out = new ArrayList<>();
if (arr == null || arr.isNull()) {
return out;
}
if (!arr.isArray()) {
throw new BadRequest(field + " must be an array");
}
for (JsonNode item : arr) {
String name;
String source;
if (item.isTextual()) {
// Optional convenience form:
// "jsSources": ["console.log(1);"]
name = field + "-" + out.size() + ".js";
source = item.textValue();
} else if (item.isObject()) {
JsonNode nameNode = item.get("name");
JsonNode sourceNode = item.get("source");
if (sourceNode == null || !sourceNode.isTextual()) {
throw new BadRequest(field + " item must include textual source");
}
if (nameNode != null && nameNode.isTextual() && !nameNode.textValue().isBlank()) {
name = nameNode.textValue();
} else {
name = field + "-" + out.size() + ".js";
}
source = sourceNode.textValue();
} else {
throw new BadRequest(field + " items must be strings or objects");
}
out.add(SourceFile.fromCode(name, source));
}
return out;
}
private static Path safePath(String value) throws BadRequest { private static Path safePath(String value) throws BadRequest {
Path p = ROOT.resolve(value).normalize(); Path p = ROOT.resolve(value).normalize();
if (!p.startsWith(ROOT)) { if (!p.startsWith(ROOT)) {
-6
View File
@@ -1,6 +0,0 @@
package generated
const (
IMAGE_TAG = "dev"
Timestamp = "20260611.005458"
)
+3 -17
View File
@@ -16,7 +16,7 @@ spec:
- name: registry-auth - name: registry-auth
containers: containers:
- name: web - name: web
image: registry.k8s.astropenguin.net/astrojs:IMAGE_TAG image: registry.k8s.astropenguin.net/resolver-go:IMAGE_TAG
securityContext: securityContext:
runAsGroup: 1001 runAsGroup: 1001
runAsNonRoot: true runAsNonRoot: true
@@ -24,14 +24,6 @@ spec:
env: env:
- name: DEBUG - name: DEBUG
value: "0" value: "0"
- name: FLASK_DEBUG
value: "0"
- name: FLASK_ENV
value: "production"
- name: RUN_MODE
value: "web"
- name: REDIS_CONN
value: "redis://:@localhost:6379/9"
volumeMounts: volumeMounts:
- name: cache - name: cache
mountPath: "/app/cache" mountPath: "/app/cache"
@@ -40,22 +32,16 @@ spec:
command: command:
- sh - sh
- -c - -c
- wget -qO - http://127.0.0.1:5000/rjs/System | grep -q function - wget -qO - http://127.0.0.1:5000/health
- name: redis - name: redis
image: redis:6.0.8-alpine image: redis:6.0.8-alpine
- name: compiler - name: compiler
image: registry.k8s.astropenguin.net/astrojs:IMAGE_TAG image: registry.k8s.astropenguin.net/closure-api:IMAGE_TAG
securityContext: securityContext:
runAsGroup: 1001
runAsNonRoot: true runAsNonRoot: true
runAsUser: 1001
env: env:
- name: RUN_MODE
value: "tasks"
- name: DEBUG - name: DEBUG
value: "0" value: "0"
- name: REDIS_CONN
value: "redis://:@localhost:6379/9"
volumeMounts: volumeMounts:
- name: cache - name: cache
mountPath: "/app/cache" mountPath: "/app/cache"
+2
View File
@@ -7,6 +7,7 @@ CLOSURE_NAME = closure-api
build-closure: build-closure:
docker build \ docker build \
-f $(CLOSURE_SRC_DIR)/Dockerfile \ -f $(CLOSURE_SRC_DIR)/Dockerfile \
--build-arg JS_SRC_DIR=$(JS_SRC_DIR) \
--build-arg JAVA_SRC_DIR=$(CLOSURE_SRC_DIR) \ --build-arg JAVA_SRC_DIR=$(CLOSURE_SRC_DIR) \
--build-arg CLOSURE_NAME=$(CLOSURE_NAME) \ --build-arg CLOSURE_NAME=$(CLOSURE_NAME) \
--load \ --load \
@@ -16,6 +17,7 @@ push-closure: ensure-buildx
docker buildx build \ docker buildx build \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
-f $(CLOSURE_SRC_DIR)/Dockerfile \ -f $(CLOSURE_SRC_DIR)/Dockerfile \
--build-arg JS_SRC_DIR=$(JS_SRC_DIR) \
--build-arg JAVA_SRC_DIR=$(CLOSURE_SRC_DIR) \ --build-arg JAVA_SRC_DIR=$(CLOSURE_SRC_DIR) \
--build-arg CLOSURE_NAME=$(CLOSURE_NAME) \ --build-arg CLOSURE_NAME=$(CLOSURE_NAME) \
-t $(CLOSURE_IMAGE_NAME):$(CLOSURE_IMAGE_TAG) \ -t $(CLOSURE_IMAGE_NAME):$(CLOSURE_IMAGE_TAG) \
+1 -1
View File
@@ -3,7 +3,7 @@ RESOLVER_IMAGE_TAG ?= dev
GO_SRC_DIR ?= ./resolver-go GO_SRC_DIR ?= ./resolver-go
BUILDINFO_FILE := internal/generated/buildinfo_gen.go BUILDINFO_FILE := $(GO_SRC_DIR)/internal/generated/buildinfo_gen.go
.buildinfo: .buildinfo:
@mkdir -p $(dir $(BUILDINFO_FILE)) @mkdir -p $(dir $(BUILDINFO_FILE))
-24
View File
@@ -1,24 +0,0 @@
FROM alpine:3.15.3
WORKDIR /app
RUN mkdir -p /opt/utils
RUN apk add --update bash python3 uwsgi uwsgi-python openjdk11-jre-headless; python3 -m ensurepip
RUN echo "www-data:x:1001:1001:www-data:/var/www:/usr/sbin/nologin" >> /etc/passwd; echo "www-data:x:1001:" >> /etc/group
RUN pip3 install Flask redis compressinja Celery
ADD [ "https://github.com/tgckpg/BotanJS/releases/download/compressors/closure.jar" \
, "https://github.com/tgckpg/BotanJS/releases/download/compressors/yuicompressor.jar" \
, "/opt/utils/" ]
COPY . /app/
RUN chmod 644 /opt/utils/*.jar; \
chown www-data:www-data . -R
USER www-data
EXPOSE 5000
ENTRYPOINT ["setup/docker.start"]
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env python3
import os, sys
sys.path.append( os.path.abspath( "." ) )
from botanjs.service.jwork import app, JWork
from botanjs.config import Config as config
SiteRoot = os.path.abspath( "." )
# Setting the SiteRoot for config
config["Paths"]["SiteRoot"] = SiteRoot
bmap = os.path.join( config["Paths"]["Cache"], "botanjs", "bmap.xml" )
app.conf.update( broker_url = config["BotanJS"]["CeleryBroker"] )
JWork.buildClassMap.delay( config["BotanJS"]["SrcDir"], bmap )
View File
-3
View File
@@ -1,3 +0,0 @@
```
Please initialize your virtualenv here
```
View File
-23
View File
@@ -1,23 +0,0 @@
#!env/bin/python
import os
from botanjs.config import Config as config, DEBUG
from subprocess import Popen
from botanjs.service.webapi import WebAPI
SiteRoot = os.path.abspath( "." )
# Setting the SiteRoot for config
config["Paths"]["SiteRoot"] = SiteRoot
service = WebAPI(
jsCache = config["Paths"]["Cache"]
, jsRoot = config["BotanJS"]["SrcDir"]
, brokerURL = config["BotanJS"]["CeleryBroker"]
)
application = service.app
if __name__ == "__main__":
application.config["DEBUG"] = DEBUG
application.run( host = config["Service"]["BindAddress"], port = config["Service"]["Port"] )
-22
View File
@@ -1,22 +0,0 @@
[Service]
BindAddress = 0.0.0.0
Port = 5000
[Env]
Debug = False
[Redis]
ConnStr = redis://:@localhost:6379/9
[Paths]
Runtime = ${SiteRoot}
Log = ${SiteRoot}/logs
Cache = ${SiteRoot}/cache
[BotanJS]
SrcDir = ${Paths:Runtime}/botanjs/src
CeleryBroker = ${Redis:ConnStr}
ClosureCompiler = /opt/utils/closure.jar
YuiCompressor = /opt/utils/yuicompressor.jar
-15
View File
@@ -1,15 +0,0 @@
# Absolute or relative path to the 'celery' command:
CELERY_BIN="BIN_ROOT/celery"
CELERYD_NODES="w1 w2"
CELERY_APP="botanjs.service.jwork"
# How to call manage.py
CELERYD_MULTI="multi"
# - %n will be replaced with the first part of the nodename.
# # - %I will be replaced with the current child process index
# # and is important when using the prefork pool to avoid race conditions.
CELERYD_PID_FILE="RUN_ROOT/tasks-%n.pid"
CELERYD_LOG_FILE="PROJ_ROOT/logs/tasks-%n.log"
CELERYD_LOG_LEVEL="INFO"
-19
View File
@@ -1,19 +0,0 @@
[Unit]
Description=BotanJS Compiler Tasks
After=network.target
[Service]
Type=forking
EnvironmentFile=-ETC_ROOT/celery.conf
WorkingDirectory=PROJ_ROOT
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
[Install]
WantedBy=multi-user.target
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
[[ $CONFIG ]] && return
CONFIG=1
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SCRIPT_DIR="$( realpath "$SCRIPT_DIR" )"
PROJ_ROOT="$( cd "$SCRIPT_DIR/../" && pwd )"
ENV_ROOT="$PROJ_ROOT/env"
BIN_ROOT="$ENV_ROOT/bin"
ETC_ROOT="$ENV_ROOT/etc"
RUN_ROOT="$ENV_ROOT/run"
PYTHON="$BIN_ROOT/python3"
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
INST_DIR=$( dirname "${BASH_SOURCE[0]}" )
source "$INST_DIR/config"
cd $PROJ_ROOT
mkdir -p cache/botanjs
case "$RUN_MODE" in
"web")
./botan-rebuild.py
uwsgi \
--plugins-dir /usr/lib/uwsgi/ --need-plugin python \
--http-socket :5000 \
--wsgi-file main.py \
--callable application --master \
--listen 4096 \
--processes 1 --threads 2
;;
"tasks")
source "$INST_DIR/celery.conf"
celery -A ${CELERY_APP} worker -n worker1@%h \
--loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} \
& celery -A ${CELERY_APP} worker -n worker1@%h \
--loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}
;;
*)
echo "RUN_MODE is missing"
exit 1
;;
esac
-21
View File
@@ -1,21 +0,0 @@
#!/bin/bash
INST_DIR=$( dirname "${BASH_SOURCE[0]}" )
source "$INST_DIR/config"
mkdir -p "$RUN_ROOT"
mkdir -p "$ETC_ROOT"
sed -e "s|PROJ_ROOT|$PROJ_ROOT|g" \
-e "s|BIN_ROOT|$BIN_ROOT|g" \
-e "s|RUN_ROOT|$RUN_ROOT|g" \
"$INST_DIR/celery.conf" > "$ETC_ROOT/celery.conf"
sed -e "s|PROJ_ROOT|$PROJ_ROOT|g" \
-e "s|ETC_ROOT|$ETC_ROOT|g" \
-e "s|RUN_AS|$RUN_AS|g" \
"$INST_DIR/compiler-tasks.service" > $HOME/.config/systemd/user/botanjs-tasks.service
systemctl --user enable botanjs-tasks.service
systemctl --user daemon-reload
systemctl --user start botanjs-tasks.service
-59
View File
@@ -1,59 +0,0 @@
#!env/bin/python
import os, sys
sys.path.append( os.path.abspath( "." ) )
from botanjs.service.jwork import app, JWork
from botanjs.config import Config as config
SiteRoot = os.path.abspath( "." )
# Setting the SiteRoot for config
config["Paths"]["SiteRoot"] = SiteRoot
jsCache = config["Paths"]["Cache"]
jsRoot = config["BotanJS"]["SrcDir"]
bmap = os.path.join( jsCache, "botanjs", "bmap.xml" )
app.conf.update( broker_url = config["BotanJS"]["CeleryBroker"] )
JWork.buildClassMap.delay( jsRoot, bmap )
from botanjs.service.jclassresv import BotanClassResolver as JCResv
import unittest
class TestStringMethods( unittest.TestCase ):
# Run each twice to test the cache capabilities
def test_ojscall( self ):
srv = JCResv( JWork, jsRoot, bmap, jsCache )
for _ in range(0,2):
s = srv.getAPI( "System", mode = "rjs" )
if not ( "BotanJS.define( \"System\" );" in s ):
print( "A---------------------" )
print( s )
print( "B---------------------" )
self.assertTrue( False)
def test_import( self ):
srv = JCResv( JWork, jsRoot, bmap, jsCache )
for _ in range(0,2):
s = srv.getAPI( "System.Policy", mode = "rjs" )
self.assertTrue( "BotanJS.define( \"System.Policy\" );" in s )
self.assertTrue( "BotanJS.define( \"System.Global\" );" in s )
def test_cssInheritance( self ):
srv = JCResv( JWork, jsRoot, bmap, jsCache )
for _ in range(0,2):
s = srv.getAPI( "System", mode = "rcss" )
self.assertTrue( "/* @ */" in s )
# def test_jsZCalls( self ):
# srv = JCResv( JWork, jsRoot, bmap, jsCache )
# for _ in range(0,2):
# s = srv.getAPI( "eJx1zsEKgzAQBNAfKvsPaaj0YE/6A4tuJbDJlM2K9O/rpcUKOQ6PYSZUN9DgbE9WpZtKluJ0F57FLuFfe35jdXpwKp1xlrN/2x3gv/ZVsVBEfqHsVmmQyRNKQ6Nhm0dejtyYPVowT5PKHl2qN36PGyJ0zeUDs1BbKA==", mode = "css" )
# print( s )
if __name__ == '__main__':
unittest.main()
-14
View File
@@ -1,14 +0,0 @@
FROM astrojs/jre-nanoserver-20h2:latest
RUN pip3 install Flask redis compressinja celery
RUN New-Item -ItemType Directory -Path /opt/utils; \
New-Item -ItemType Directory -Path /app/cache -Force; \
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; \
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/tgckpg/BotanJS/releases/download/compressors/closure.jar' -OutFile '/opt/utils/closure.jar' ; \
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/tgckpg/BotanJS/releases/download/compressors/yuicompressor.jar' -OutFile '/opt/utils/yuicompressor.jar' ;
COPY . /app
WORKDIR /app
EXPOSE 5000
-9
View File
@@ -1,9 +0,0 @@
version: '3.9'
services:
python:
build: pyrt
image: astrojs/pyrt-nanoserver-20h2
jre:
build: jre
image: astrojs/jre-nanoserver-20h2
-36
View File
@@ -1,36 +0,0 @@
version: '3.9'
services:
redis:
container_name: astrojsdev_redis
build: redis
web:
container_name: astrojsdev_app
image: astrojs/app
hostname: astrojs.default
build:
context: ../
dockerfile: windows/app/Dockerfile
environment:
DEBUG: "1"
REDIS_CONN: redis://:@redis:6379/9
command: [ "python", "main.py" ] # [ "ping", "127.0.0.1", "-n", "9999" ]
depends_on:
- redis
ports:
- 5000:5000
volumes:
- cache:C:/app/cache
tasks:
container_name: astrojsdev_compiler
image: astrojs/app
environment:
REDIS_CONN: redis://:@redis:6379/9
command: [ "celery", "-A", "botanjs.service.jwork", "worker", "-l", "info", "--pool=solo" ]
depends_on:
- redis
- web
volumes:
- cache:C:/app/cache
volumes:
cache:
-9
View File
@@ -1,9 +0,0 @@
FROM astrojs/pyrt-nanoserver-20h2:latest as base
ENV JAVA_HOME=C:\\openjdk-11
ENV JAVA_VERSION=11.0.12
RUN setx PATH "$Env:JAVA_HOME\bin`;$Env:Path" /M
COPY --from=openjdk:11-jre-nanoserver /openjdk-11 /openjdk-11
RUN echo Verifying install ... && echo java --version && java --version && echo Complete.
-54
View File
@@ -1,54 +0,0 @@
FROM mcr.microsoft.com/powershell:nanoserver-20h2
SHELL [ "pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';" ]
RUN $url = 'https://www.python.org/ftp/python/3.7.6/python-3.7.6-embed-amd64.zip'; \
Write-host "downloading: $url"; \
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; \
New-Item -ItemType Directory /installer > $null ; \
Invoke-WebRequest -Uri $url -outfile /installer/Python.zip -verbose; \
Expand-Archive /installer/Python.zip -DestinationPath /Python; \
Move-Item /Python/python37._pth /Python/python37._pth.save
### Begin workaround ###
# Note that changing user on nanoserver is not recommended
# See, https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-base-images#base-image-differences
# But we are working around a bug introduced in the nanoserver image introduced in 20h2
USER ContainerAdministrator
# This is basically the correct code except for the /M
RUN setx PATH "$Env:Path`C:\Python`;C:\Python\Scripts`;" /M
# We can't
# USER ContainerUser
### End workaround ###
# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
ENV PYTHON_PIP_VERSION 21.2.4
# https://github.com/pypa/get-pip
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/4b85d3add912c861aea4a9feaae737a5b7b9cb1c/public/get-pip.py
ENV PYTHON_GET_PIP_SHA256 ced8c71489cd46c511677bfe423f37eb88f08f29e9af36ef2679091ec7122d4f
RUN Write-Host ('Downloading get-pip.py ({0}) ...' -f $env:PYTHON_GET_PIP_URL); \
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \
Invoke-WebRequest -Uri $env:PYTHON_GET_PIP_URL -OutFile 'get-pip.py'; \
Write-Host ('Verifying sha256 ({0}) ...' -f $env:PYTHON_GET_PIP_SHA256); \
if ((Get-FileHash 'get-pip.py' -Algorithm sha256).Hash -ne $env:PYTHON_GET_PIP_SHA256) { \
Write-Host 'FAILED!'; \
exit 1; \
}; \
\
Write-Host ('Installing pip=={0} ...' -f $env:PYTHON_PIP_VERSION); \
python get-pip.py \
--disable-pip-version-check \
--no-cache-dir \
('pip=={0}' -f $env:PYTHON_PIP_VERSION) \
; \
Remove-Item get-pip.py -Force; \
\
Write-Host 'Verifying pip install ...'; \
pip --version; \
\
Write-Host 'Complete.'
CMD [ "python.exe" ]
-27
View File
@@ -1,27 +0,0 @@
FROM mcr.microsoft.com/powershell:nanoserver-20h2
SHELL [ "pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';" ]
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; \
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/tporadowski/redis/releases/download/v5.0.10/Redis-x64-5.0.10.zip' -OutFile 'Redis-x64-5.0.10.zip' ; \
Expand-Archive Redis-x64-5.0.10.zip -dest 'C:\\Program Files\\Redis\\' ; \
Remove-Item Redis-x64-5.0.10.zip -Force
User ContainerAdministrator
RUN setx Path "C:\Program` Files\Redis`;$Env:Path" /M;
User ContainerUser
WORKDIR 'C:\\Program Files\\Redis'
RUN Get-Content redis.windows.conf | Where { $_ -notmatch 'bind 127.0.0.1' } | Set-Content redis.openport.conf ; \
Get-Content redis.openport.conf | Where { $_ -notmatch 'protected-mode yes' } | Set-Content redis.unprotected.conf ; \
Add-Content redis.unprotected.conf 'protected-mode no' ; \
Add-Content redis.unprotected.conf 'bind 0.0.0.0' ; \
Get-Content redis.unprotected.conf
EXPOSE 6379
# Define our command to be run when launching the container
CMD .\\redis-server.exe .\\redis.unprotected.conf --port 6379 ; \
Write-Host Redis Started... ; \
while ($true) { Start-Sleep -Seconds 3600 }
+1 -1
View File
@@ -1,4 +1,4 @@
# botanres-go # resolver-go
Go rewrite for the old BotanJS dynamic resource resolver. Go rewrite for the old BotanJS dynamic resource resolver.
+60 -5
View File
@@ -1,13 +1,16 @@
package main package main
import ( import (
"crypto/md5"
"encoding/hex"
"flag" "flag"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"github.com/tgckpg/botanres-go/internal/generated" "github.com/tgckpg/resolver-go/internal/closure"
"github.com/tgckpg/botanres-go/internal/resolver" "github.com/tgckpg/resolver-go/internal/generated"
"github.com/tgckpg/resolver-go/internal/resolver"
) )
func main() { func main() {
@@ -20,13 +23,24 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
h := handler{r: r} h := handler{
r: r,
closure: closure.NewCompileCache(2),
}
http.HandleFunc("/", h.index) http.HandleFunc("/", h.index)
log.Printf("botan-api listening on %s", *addr) log.Printf("botan-api listening on %s", *addr)
log.Fatal(http.ListenAndServe(*addr, nil)) log.Fatal(http.ListenAndServe(*addr, nil))
} }
type handler struct{ r *resolver.Resolver } type handler struct {
r *resolver.Resolver
closure *closure.CompileCache
}
func hashStrings(parts []string) string {
sum := md5.Sum([]byte(strings.Join(parts, "|")))
return hex.EncodeToString(sum[:])
}
func (h handler) index(w http.ResponseWriter, req *http.Request) { func (h handler) index(w http.ResponseWriter, req *http.Request) {
path := strings.Trim(req.URL.Path, "/") path := strings.Trim(req.URL.Path, "/")
@@ -61,7 +75,46 @@ func (h handler) index(w http.ResponseWriter, req *http.Request) {
return return
} }
log.Println(res.JSFiles) if outMode == resolver.ModeJS {
fileHashes := make([]string, 0, len(res.JSFiles))
for _, f := range res.JSFiles {
fileHashes = append(fileHashes, f.JSHash)
}
hash := hashStrings(fileHashes)
if compiled, ok := h.closure.Get(hash); ok {
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("X-Botan-Compiled", "hit")
w.Write(compiled)
return
}
jsFiles := make([]string, 0, len(res.JSFiles))
for _, f := range res.JSFiles {
jsFiles = append(jsFiles, f.Src)
}
jsExterns, err := h.r.GetExterns(generated.Externs)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
h.closure.Enqueue(closure.CompileJob{
Hash: res.Hash,
Mode: "js",
ExternSources: jsExterns,
JSSources: []closure.SourceInput{
{
Name: "botanjs-" + res.Hash + ".js",
Source: string(res.Content),
},
},
Defines: map[string]any{
"DEBUG": false,
},
})
}
// Compatibility flags: // Compatibility flags:
// rjs/rcss/ojs/ocss => content // rjs/rcss/ojs/ocss => content
@@ -73,5 +126,7 @@ func (h handler) index(w http.ResponseWriter, req *http.Request) {
return return
} }
w.Header().Set("Content-Type", res.ContentType) w.Header().Set("Content-Type", res.ContentType)
w.Header().Set("X-Botan-Compiled", "miss")
_, _ = w.Write(res.Content) _, _ = w.Write(res.Content)
} }
@@ -7,7 +7,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/tgckpg/botanres-go/internal/classmap" "github.com/tgckpg/resolver-go/internal/classmap"
) )
func main() { func main() {
@@ -36,9 +36,9 @@ func main() {
func render(pkg string, m *classmap.Map) string { func render(pkg string, m *classmap.Map) string {
var b strings.Builder var b strings.Builder
b.WriteString("// Code generated by botan-gen; DO NOT EDIT.\n") b.WriteString("// Code generated by classmap-gen; DO NOT EDIT.\n")
b.WriteString("package " + pkg + "\n\n") b.WriteString("package " + pkg + "\n\n")
b.WriteString("import \"github.com/tgckpg/botanres-go/internal/classmap\"\n\n") b.WriteString("import \"github.com/tgckpg/resolver-go/internal/classmap\"\n\n")
b.WriteString("var ClassMap = &classmap.Map{\nSymbols: map[string]classmap.Symbol{\n") b.WriteString("var ClassMap = &classmap.Map{\nSymbols: map[string]classmap.Symbol{\n")
for _, s := range classmap.SortedSymbols(m) { for _, s := range classmap.SortedSymbols(m) {
fmt.Fprintf(&b, "%q: {Name:%q, Kind:%q, Parent:%q, Imports:%#v, Resource: classmap.Resource{Src:%q, JSHash:%q, CSSHash:%q}},\n", fmt.Fprintf(&b, "%q: {Name:%q, Kind:%q, Parent:%q, Imports:%#v, Resource: classmap.Resource{Src:%q, JSHash:%q, CSSHash:%q}},\n",
@@ -61,6 +61,6 @@ func dir(path string) string {
} }
func fatal(err error) { func fatal(err error) {
fmt.Fprintln(os.Stderr, "botan-gen:", err) fmt.Fprintln(os.Stderr, "classmap-gen:", err)
os.Exit(1) os.Exit(1)
} }
+87
View File
@@ -0,0 +1,87 @@
package main
import (
"flag"
"fmt"
"go/format"
"io/fs"
"os"
"path/filepath"
"strings"
)
func main() {
src := flag.String("src", "./src", "BotanJS source root")
out := flag.String("out", "internal/generated/externs_gen.go", "generated Go output")
pkg := flag.String("pkg", "generated", "generated package name")
flag.Parse()
if err := os.MkdirAll(filepath.Dir(*out), 0o755); err != nil {
fatal(err)
}
code, err := render(*pkg, *src)
if err != nil {
fatal(err)
}
fmted, err := format.Source([]byte(code))
if err != nil {
_, _ = os.Stderr.WriteString(code)
fatal(err)
}
if err := os.WriteFile(*out, fmted, 0o644); err != nil {
fatal(err)
}
}
func render(pkg string, root string) (string, error) {
var b strings.Builder
root = filepath.Clean(root)
b.WriteString("// Code generated by externs-gen; DO NOT EDIT.\n")
b.WriteString("package " + pkg + "\n\n")
b.WriteString("var Externs = []string{\n")
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if filepath.Ext(path) != ".js" {
return nil
}
if filepath.Base(filepath.Dir(path)) != "externs" {
return nil
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
// Generated data should be slash-normalized even on Windows.
rel = filepath.ToSlash(rel)
fmt.Fprintf(&b, "\t%q,\n", rel)
return nil
})
if err != nil {
return "", err
}
b.WriteString("}\n")
return b.String(), nil
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "externs-gen:", err)
os.Exit(1)
}
+4 -1
View File
@@ -21,10 +21,13 @@ RUN --mount=type=cache,target=/go/pkg/mod \
GOARCH=$TARGETARCH \ GOARCH=$TARGETARCH \
go build -trimpath -o /out/botan-api -ldflags='-s -w' ./cmd/botan-api go build -trimpath -o /out/botan-api -ldflags='-s -w' ./cmd/botan-api
RUN mkdir -p /out/tmp && chmod 1777 /out/tmp
FROM scratch FROM scratch
COPY --from=build /out/botan-api /usr/local/bin/botan-api COPY --from=build /out/botan-api /usr/local/bin/botan-api
COPY "botanjs/src" "./src" COPY --from=build /workspace/src "./src"
COPY --from=build /out/tmp /tmp
EXPOSE 8080/tcp EXPOSE 8080/tcp
ENTRYPOINT ["/usr/local/bin/botan-api", "-src", "./src", "-addr", ":8080"] ENTRYPOINT ["/usr/local/bin/botan-api", "-src", "./src", "-addr", ":8080"]
+7 -3
View File
@@ -14,11 +14,15 @@ COPY "botanjs/src" "./src"
COPY "resolver-go/cmd" "./cmd" COPY "resolver-go/cmd" "./cmd"
COPY "resolver-go/internal" "./internal" COPY "resolver-go/internal" "./internal"
RUN go run ./cmd/botan-gen \ RUN go run ./cmd/classmap-gen \
-src ./src \ -src ./src \
-out internal/generated/classmap_gen.go -out internal/generated/classmap_gen.go
RUN mkdir /out && cp internal/generated/classmap_gen.go /out RUN go run ./cmd/externs-gen \
-src ./src \
-out internal/generated/externs_gen.go
RUN mkdir /out && cp internal/generated/*_gen.go /out
FROM scratch FROM scratch
COPY --from=build /out/classmap_gen.go ./ COPY --from=build /out/*_gen.go ./
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/tgckpg/botanres-go module github.com/tgckpg/resolver-go
go 1.26 go 1.26
+85
View File
@@ -0,0 +1,85 @@
package closure
import (
"context"
"errors"
"time"
)
func NewCompileCache(workers int) *CompileCache {
c := &CompileCache{
client: NewClientFromEnv(),
states: make(map[string]CompileState),
results: make(map[string][]byte),
errors: make(map[string]error),
jobs: make(chan CompileJob, 128),
}
for i := 0; i < workers; i++ {
go c.worker()
}
return c
}
func (c *CompileCache) Get(hash string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.states[hash] != CompileReady {
return nil, false
}
return c.results[hash], true
}
func (c *CompileCache) Enqueue(job CompileJob) {
c.mu.Lock()
switch c.states[job.Hash] {
case CompilePending, CompileReady:
c.mu.Unlock()
return
}
c.states[job.Hash] = CompilePending
c.mu.Unlock()
select {
case c.jobs <- job:
default:
// Queue full. Don't block request path.
c.mu.Lock()
c.states[job.Hash] = CompileMissing
c.errors[job.Hash] = errors.New("compile queue full")
c.mu.Unlock()
}
}
func (c *CompileCache) worker() {
for job := range c.jobs {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
req := CompileRequest{
ExternSources: job.ExternSources,
JSSources: job.JSSources,
Defines: job.Defines,
}
c.client.DebugPrintCurl(ctx, req)
out, err := c.client.Compile(ctx, req)
cancel()
c.mu.Lock()
if err != nil {
c.states[job.Hash] = CompileFailed
c.errors[job.Hash] = err
} else {
c.states[job.Hash] = CompileReady
c.results[job.Hash] = out
delete(c.errors, job.Hash)
}
c.mu.Unlock()
}
}
+107
View File
@@ -0,0 +1,107 @@
package closure
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
)
type Client struct {
endpoint string
http *http.Client
}
func NewClientFromEnv() *Client {
endpoint := os.Getenv("CLOSURE_ENDPOINT")
if endpoint == "" {
endpoint = "http://closure-svc:8080/compile"
}
return &Client{
endpoint: endpoint,
http: &http.Client{
Timeout: 70 * time.Second,
},
}
}
func (c *Client) Compile(ctx context.Context, reqBody CompileRequest) ([]byte, error) {
body, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
c.endpoint,
bytes.NewReader(body),
)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return nil, fmt.Errorf("closure failed: %s: %s", resp.Status, body)
}
return io.ReadAll(resp.Body)
}
func (c *Client) DebugPrintCurl(ctx context.Context, reqBody CompileRequest) {
if os.Getenv("CLOSURE_DEBUG_CURL") == "" {
return
}
body, err := json.MarshalIndent(reqBody, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "closure debug: marshal payload failed: %v\n", err)
return
}
tmpDir := os.Getenv("CLOSURE_DEBUG_DIR")
if tmpDir == "" {
tmpDir = os.TempDir()
}
path := filepath.Join(
tmpDir,
fmt.Sprintf("closure-request-%d.json", time.Now().UnixNano()),
)
if err := os.WriteFile(path, body, 0o600); err != nil {
fmt.Fprintf(os.Stderr, "closure debug: write payload failed: %v\n", err)
return
}
fmt.Fprintf(
os.Stderr,
"closure debug curl:\n curl -v -X POST %s -H 'Content-Type: application/json' --data-binary @%s\n",
shellQuote(c.endpoint),
shellQuote(path),
)
if deadline, ok := ctx.Deadline(); ok {
fmt.Fprintf(os.Stderr, "closure debug deadline: %s\n", deadline.Format(time.RFC3339Nano))
}
}
func shellQuote(s string) string {
return strconv.Quote(s)
}
+41
View File
@@ -0,0 +1,41 @@
package closure
import "sync"
type CompileState int
const (
CompileMissing CompileState = iota
CompilePending
CompileReady
CompileFailed
)
type CompileRequest struct {
ExternSources []SourceInput `json:"externSources,omitempty"`
JSSources []SourceInput `json:"jsSources"`
Defines map[string]any `json:"defines,omitempty"`
}
type SourceInput struct {
Name string `json:"name"`
Source string `json:"source"`
}
type CompileJob struct {
Hash string
Mode string
ExternSources []SourceInput
JSSources []SourceInput
Defines map[string]any
}
type CompileCache struct {
client *Client
mu sync.Mutex
states map[string]CompileState
results map[string][]byte
errors map[string]error
jobs chan CompileJob
}
@@ -2,5 +2,5 @@ package generated
const ( const (
IMAGE_TAG = "dev" IMAGE_TAG = "dev"
Timestamp = "20260610.201739" Timestamp = "20260611.192429"
) )
@@ -1,7 +1,7 @@
// Code generated by botan-gen; DO NOT EDIT. // Code generated by classmap-gen; DO NOT EDIT.
package generated package generated
import "github.com/tgckpg/botanres-go/internal/classmap" import "github.com/tgckpg/resolver-go/internal/classmap"
var ClassMap = &classmap.Map{ var ClassMap = &classmap.Map{
Symbols: map[string]classmap.Symbol{ Symbols: map[string]classmap.Symbol{
@@ -84,9 +84,9 @@ var ClassMap = &classmap.Map{
"Astro.Common.Element": {Name: "Astro.Common.Element", Kind: "class", Parent: "Astro.Common", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Common.Element": {Name: "Astro.Common.Element", Kind: "class", Parent: "Astro.Common", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.Common.Element.Footer": {Name: "Astro.Common.Element.Footer", Kind: "class", Parent: "Astro.Common.Element", Imports: []string(nil), Resource: classmap.Resource{Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"}}, "Astro.Common.Element.Footer": {Name: "Astro.Common.Element.Footer", Kind: "class", Parent: "Astro.Common.Element", Imports: []string(nil), Resource: classmap.Resource{Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"}},
"Astro.Mechanism": {Name: "Astro.Mechanism", Kind: "class", Parent: "Astro", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Mechanism": {Name: "Astro.Mechanism", Kind: "class", Parent: "Astro", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.Mechanism.CharacterCloud": {Name: "Astro.Mechanism.CharacterCloud", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"Dandelion"}, Resource: classmap.Resource{Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "540a2085798928418a54e10d56bc037863ee57a9", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"}}, "Astro.Mechanism.CharacterCloud": {Name: "Astro.Mechanism.CharacterCloud", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"Dandelion"}, Resource: classmap.Resource{Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "281b59ca621d35d254abbddc1f088870ed61cb28", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"}},
"Astro.Mechanism.CharacterCloud.create": {Name: "Astro.Mechanism.CharacterCloud.create", Kind: "method", Parent: "Astro.Mechanism.CharacterCloud", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Mechanism.CharacterCloud.create": {Name: "Astro.Mechanism.CharacterCloud.create", Kind: "method", Parent: "Astro.Mechanism.CharacterCloud", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.Mechanism.Parallax": {Name: "Astro.Mechanism.Parallax", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"System.Cycle", "Dandelion", "Dandelion.IDOMObject"}, Resource: classmap.Resource{Src: "Astro/Mechanism/Parallax.js", JSHash: "ba7b1816fc5d32edfaf5b93b9d35c7b2c297f011", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"}}, "Astro.Mechanism.Parallax": {Name: "Astro.Mechanism.Parallax", Kind: "class", Parent: "Astro.Mechanism", Imports: []string{"System.Cycle", "Dandelion", "Dandelion.IDOMObject"}, Resource: classmap.Resource{Src: "Astro/Mechanism/Parallax.js", JSHash: "6ac80e95f9e8ba391668e1988fe3586987ea79d0", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"}},
"Astro.Mechanism.Parallax.attach": {Name: "Astro.Mechanism.Parallax.attach", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Mechanism.Parallax.attach": {Name: "Astro.Mechanism.Parallax.attach", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.Mechanism.Parallax.cssSlide": {Name: "Astro.Mechanism.Parallax.cssSlide", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Mechanism.Parallax.cssSlide": {Name: "Astro.Mechanism.Parallax.cssSlide", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.Mechanism.Parallax.verticalSlideTo": {Name: "Astro.Mechanism.Parallax.verticalSlideTo", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.Mechanism.Parallax.verticalSlideTo": {Name: "Astro.Mechanism.Parallax.verticalSlideTo", Kind: "method", Parent: "Astro.Mechanism.Parallax", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
@@ -117,7 +117,7 @@ var ClassMap = &classmap.Map{
"Astro.utils.Date.prettyDay": {Name: "Astro.utils.Date.prettyDay", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.utils.Date.prettyDay": {Name: "Astro.utils.Date.prettyDay", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Astro.utils.Date.smstamp": {Name: "Astro.utils.Date.smstamp", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}}, "Astro.utils.Date.smstamp": {Name: "Astro.utils.Date.smstamp", Kind: "method", Parent: "Astro.utils.Date", Imports: []string(nil), Resource: classmap.Resource{Src: "", JSHash: "", CSSHash: ""}},
"Components": {Name: "Components", Kind: "class", Parent: "", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/_this.js", JSHash: "f4cb7babe62a5cdae34d2c4ebcb55071e81da4ff", CSSHash: "1"}}, "Components": {Name: "Components", Kind: "class", Parent: "", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/_this.js", JSHash: "f4cb7babe62a5cdae34d2c4ebcb55071e81da4ff", CSSHash: "1"}},
"Components.Console": {Name: "Components.Console", Kind: "class", Parent: "Components", Imports: []string{"System.utils.Perf", "System.Cycle", "System.Cycle.TICK", "System.Global", "System.Log", "System.Debug", "Dandelion", "Dandelion.IDOMElement", "Components.DockPanel"}, Resource: classmap.Resource{Src: "Components/Console.js", JSHash: "115be15db72bd22f8222510e7bf4ce0c741028fb", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"}}, "Components.Console": {Name: "Components.Console", Kind: "class", Parent: "Components", Imports: []string{"System.utils.Perf", "System.Cycle", "System.Cycle.TICK", "System.Global", "System.Log", "System.Debug", "Dandelion", "Dandelion.IDOMElement", "Components.DockPanel"}, Resource: classmap.Resource{Src: "Components/Console.js", JSHash: "9e4ba7f921f39c85f264e416669134120d4668ea", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"}},
"Components.DockPanel": {Name: "Components.DockPanel", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle", "System.utils.DataKey", "Dandelion", "Dandelion.IDOMElement"}, Resource: classmap.Resource{Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"}}, "Components.DockPanel": {Name: "Components.DockPanel", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle", "System.utils.DataKey", "Dandelion", "Dandelion.IDOMElement"}, Resource: classmap.Resource{Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"}},
"Components.MessageBox": {Name: "Components.MessageBox", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle.Trigger", "Dandelion", "Dandelion.IDOMObject", "System.utils.EventKey", "Dandelion.CSSAnimations"}, Resource: classmap.Resource{Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"}}, "Components.MessageBox": {Name: "Components.MessageBox", Kind: "class", Parent: "Components", Imports: []string{"System.Cycle.Trigger", "Dandelion", "Dandelion.IDOMObject", "System.utils.EventKey", "Dandelion.CSSAnimations"}, Resource: classmap.Resource{Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"}},
"Components.Mouse": {Name: "Components.Mouse", Kind: "class", Parent: "Components", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/Mouse/_this.js", JSHash: "9ec5ced53a20ea1d057e2142668e27e5df7d49f6", CSSHash: "1"}}, "Components.Mouse": {Name: "Components.Mouse", Kind: "class", Parent: "Components", Imports: []string(nil), Resource: classmap.Resource{Src: "Components/Mouse/_this.js", JSHash: "9ec5ced53a20ea1d057e2142668e27e5df7d49f6", CSSHash: "1"}},
@@ -379,8 +379,8 @@ var ClassMap = &classmap.Map{
"Astro/Blog/SharedStyle.js": {Src: "Astro/Blog/SharedStyle.js", JSHash: "e946130da823a5b2d089b5b416c13b628eb1637d", CSSHash: "f8ff15304a5e38e199b713bac48e282d2bf10f45"}, "Astro/Blog/SharedStyle.js": {Src: "Astro/Blog/SharedStyle.js", JSHash: "e946130da823a5b2d089b5b416c13b628eb1637d", CSSHash: "f8ff15304a5e38e199b713bac48e282d2bf10f45"},
"Astro/Bootstrap.js": {Src: "Astro/Bootstrap.js", JSHash: "51bfb270eadd20b4b71372ae2d20dcf3d20575db", CSSHash: "e650f4de36f799af80ca0633d66351fb9333d620"}, "Astro/Bootstrap.js": {Src: "Astro/Bootstrap.js", JSHash: "51bfb270eadd20b4b71372ae2d20dcf3d20575db", CSSHash: "e650f4de36f799af80ca0633d66351fb9333d620"},
"Astro/Common/Element/Footer.js": {Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"}, "Astro/Common/Element/Footer.js": {Src: "Astro/Common/Element/Footer.js", JSHash: "80f69d85c399bdc04d3b2055d1ebbe9ee77a852a", CSSHash: "c26f4238a6a4f2fc0bcbd9ac86141d12522552a4"},
"Astro/Mechanism/CharacterCloud.js": {Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "540a2085798928418a54e10d56bc037863ee57a9", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"}, "Astro/Mechanism/CharacterCloud.js": {Src: "Astro/Mechanism/CharacterCloud.js", JSHash: "281b59ca621d35d254abbddc1f088870ed61cb28", CSSHash: "7cae98eaf8e3d5d1cd7fadaa6806ce3d65d74d92"},
"Astro/Mechanism/Parallax.js": {Src: "Astro/Mechanism/Parallax.js", JSHash: "ba7b1816fc5d32edfaf5b93b9d35c7b2c297f011", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"}, "Astro/Mechanism/Parallax.js": {Src: "Astro/Mechanism/Parallax.js", JSHash: "6ac80e95f9e8ba391668e1988fe3586987ea79d0", CSSHash: "f8c02f4ec1be082e02c51f0b5ea39cbdd5b0e49f"},
"Astro/Penguin/Layout/MainFrame.js": {Src: "Astro/Penguin/Layout/MainFrame.js", JSHash: "b2f58fc9394745c1e47dfb95c4043f59f76acc1a", CSSHash: "e87b69ab9e725e4801065850790670d76f0a6d18"}, "Astro/Penguin/Layout/MainFrame.js": {Src: "Astro/Penguin/Layout/MainFrame.js", JSHash: "b2f58fc9394745c1e47dfb95c4043f59f76acc1a", CSSHash: "e87b69ab9e725e4801065850790670d76f0a6d18"},
"Astro/Penguin/Page/Docs.js": {Src: "Astro/Penguin/Page/Docs.js", JSHash: "02217e7fa4ebffe90b32002ef07da500ac02bf52", CSSHash: "5230a026499b9ab0f4f530f4975a9df89170c83b"}, "Astro/Penguin/Page/Docs.js": {Src: "Astro/Penguin/Page/Docs.js", JSHash: "02217e7fa4ebffe90b32002ef07da500ac02bf52", CSSHash: "5230a026499b9ab0f4f530f4975a9df89170c83b"},
"Astro/Penguin/Page/_this.js": {Src: "Astro/Penguin/Page/_this.js", JSHash: "66e415e546eb6ca0b84cedfd4d3b6de67c2164f3", CSSHash: "e8afcaa8c75b241cd933cfc99a648adc42f815d7"}, "Astro/Penguin/Page/_this.js": {Src: "Astro/Penguin/Page/_this.js", JSHash: "66e415e546eb6ca0b84cedfd4d3b6de67c2164f3", CSSHash: "e8afcaa8c75b241cd933cfc99a648adc42f815d7"},
@@ -393,7 +393,7 @@ var ClassMap = &classmap.Map{
"Astro/Starfall/_this.js": {Src: "Astro/Starfall/_this.js", JSHash: "77ca61d1ba806c3440cec5e26a100581259e1784", CSSHash: "1"}, "Astro/Starfall/_this.js": {Src: "Astro/Starfall/_this.js", JSHash: "77ca61d1ba806c3440cec5e26a100581259e1784", CSSHash: "1"},
"Astro/utils/Date.js": {Src: "Astro/utils/Date.js", JSHash: "45221fe447d15fd943310fe9d3d3308f884b6aec", CSSHash: "1"}, "Astro/utils/Date.js": {Src: "Astro/utils/Date.js", JSHash: "45221fe447d15fd943310fe9d3d3308f884b6aec", CSSHash: "1"},
"Astro/utils/_this.js": {Src: "Astro/utils/_this.js", JSHash: "21e99449ec5c1a4b6d25164db9434132aeea5ad3", CSSHash: "1"}, "Astro/utils/_this.js": {Src: "Astro/utils/_this.js", JSHash: "21e99449ec5c1a4b6d25164db9434132aeea5ad3", CSSHash: "1"},
"Components/Console.js": {Src: "Components/Console.js", JSHash: "115be15db72bd22f8222510e7bf4ce0c741028fb", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"}, "Components/Console.js": {Src: "Components/Console.js", JSHash: "9e4ba7f921f39c85f264e416669134120d4668ea", CSSHash: "452f4a36e8fd7321c7d177a311047c8b71165e48"},
"Components/DockPanel.js": {Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"}, "Components/DockPanel.js": {Src: "Components/DockPanel.js", JSHash: "c74b3081efdcbfa13300e9a49529f5664734b24e", CSSHash: "af0d91982c764ee62c46b243be68c08f2808e82b"},
"Components/MessageBox.js": {Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"}, "Components/MessageBox.js": {Src: "Components/MessageBox.js", JSHash: "db0b55a5e0b1a92b9781c2b0b13817355b8e3cdf", CSSHash: "5571fa4c2e1324dcf1f2e18df8b6105cf37dfc53"},
"Components/Mouse/Clipboard.js": {Src: "Components/Mouse/Clipboard.js", JSHash: "574a1ef17878beb21395a2eecfa75d046ff694e0", CSSHash: "1b7f85206ce1560ed23d3b270e093022e25d2e61"}, "Components/Mouse/Clipboard.js": {Src: "Components/Mouse/Clipboard.js", JSHash: "574a1ef17878beb21395a2eecfa75d046ff694e0", CSSHash: "1b7f85206ce1560ed23d3b270e093022e25d2e61"},
@@ -0,0 +1,108 @@
// Code generated by externs-gen; DO NOT EDIT.
package generated
var Externs = []string{
"externs/Astro.Blog.AstroEdit.Article.js",
"externs/Astro.Blog.AstroEdit.IPlugin.js",
"externs/Astro.Blog.AstroEdit.SmartInput.Definition.js",
"externs/Astro.Blog.AstroEdit.SmartInput.ICandidateAction.js",
"externs/Astro.Blog.AstroEdit.SmartInput.js",
"externs/Astro.Blog.AstroEdit.Visualizer.Snippet.Model.js",
"externs/Astro.Blog.AstroEdit.Visualizer.Snippet.js",
"externs/Astro.Blog.AstroEdit.Visualizer.js",
"externs/Astro.Blog.AstroEdit.js",
"externs/Astro.Blog.Components.Bubble.js",
"externs/Astro.Blog.Components.Calendar.js",
"externs/Astro.Blog.Components.SiteFile.js",
"externs/Astro.Blog.Components.js",
"externs/Astro.Blog.Config.js",
"externs/Astro.Blog.Events.Responsive.js",
"externs/Astro.Blog.Events.js",
"externs/Astro.Blog.js",
"externs/Astro.Bootstrap.js",
"externs/Astro.Mechanism.CharacterCloud.js",
"externs/Astro.Mechanism.Parallax.js",
"externs/Astro.Mechanism.js",
"externs/Astro.js",
"externs/Astro.utils.Date.js",
"externs/Astro.utils.js",
"externs/BotanEvent.js",
"externs/BotanJS.js",
"externs/Components.MessageBox.js",
"externs/Components.Mouse.Clipboard.js",
"externs/Components.Mouse.ContextMenu.js",
"externs/Components.Mouse.js",
"externs/Components.Vim.Actions.js",
"externs/Components.Vim.Controls.ActionEvent.js",
"externs/Components.Vim.Controls.js",
"externs/Components.Vim.Cursor.js",
"externs/Components.Vim.DateTime.js",
"externs/Components.Vim.IAction.js",
"externs/Components.Vim.LineBuffer.js",
"externs/Components.Vim.LineFeeder.js",
"externs/Components.Vim.State.History.js",
"externs/Components.Vim.State.Marks.js",
"externs/Components.Vim.State.Recorder.js",
"externs/Components.Vim.State.Registers.js",
"externs/Components.Vim.State.Stack.js",
"externs/Components.Vim.State.js",
"externs/Components.Vim.StatusBar.js",
"externs/Components.Vim.Syntax.Analyzer.js",
"externs/Components.Vim.Syntax.TokenMatch.js",
"externs/Components.Vim.Syntax.Word.js",
"externs/Components.Vim.Syntax.js",
"externs/Components.Vim.VimArea.js",
"externs/Components.Vim.js",
"externs/Components.js",
"externs/Dandelion.CSSAnimations.MovieClip.js",
"externs/Dandelion.CSSAnimations.js",
"externs/Dandelion.IDOMElement.js",
"externs/Dandelion.IDOMObject.js",
"externs/Dandelion.js",
"externs/Libraries.SyntaxHighLighter.Brush.js",
"externs/Libraries.SyntaxHighLighter.js",
"externs/Libraries.js",
"externs/System.Compression.Zlib.js",
"externs/System.Compression.js",
"externs/System.Cycle.Trigger.js",
"externs/System.Cycle.js",
"externs/System.Debug.js",
"externs/System.Encoding.Base64.js",
"externs/System.Encoding.Utf8.js",
"externs/System.Encoding.js",
"externs/System.Global.js",
"externs/System.Log.js",
"externs/System.Net.js",
"externs/System.Policy.js",
"externs/System.Tick.js",
"externs/System.js",
"externs/System.utils.DataKey.js",
"externs/System.utils.EventKey.js",
"externs/System.utils.IKey.js",
"externs/System.utils.Perf.js",
"externs/System.utils.js",
"externs/_3rdParty_.Recaptcha.js",
"externs/_3rdParty_.js",
"externs/_AstConf_.Article.js",
"externs/_AstConf_.AstroEdit.js",
"externs/_AstConf_.Comment.js",
"externs/_AstConf_.Control.js",
"externs/_AstConf_.Login.js",
"externs/_AstConf_.Notification.js",
"externs/_AstConf_.SiteFile.js",
"externs/_AstConf_.UserInfo.js",
"externs/_AstConf_.js",
"externs/_AstJson_.AJaxGetArticle.js",
"externs/_AstJson_.AJaxGetDrafts.js",
"externs/_AstJson_.AJaxGetFiles.Request.js",
"externs/_AstJson_.AJaxGetFiles.js",
"externs/_AstJson_.AJaxGetNotis.js",
"externs/_AstJson_.SiteFile.js",
"externs/_AstJson_.js",
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.Code.js",
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.Spoiler.js",
"externs/_AstXObject_.AstroEdit.Visualizer.Snippet.js",
"externs/_AstXObject_.AstroEdit.Visualizer.js",
"externs/_AstXObject_.AstroEdit.js",
"externs/_AstXObject_.js",
}
+60 -2
View File
@@ -14,8 +14,10 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"sync"
"github.com/tgckpg/botanres-go/internal/classmap" "github.com/tgckpg/resolver-go/internal/classmap"
"github.com/tgckpg/resolver-go/internal/closure"
) )
type OutputMode string type OutputMode string
@@ -38,6 +40,9 @@ type Result struct {
type Resolver struct { type Resolver struct {
Root string Root string
Map *classmap.Map Map *classmap.Map
externMu sync.RWMutex
externCache map[string]closure.SourceInput
} }
func New(root string, m *classmap.Map) (*Resolver, error) { func New(root string, m *classmap.Map) (*Resolver, error) {
@@ -45,7 +50,11 @@ func New(root string, m *classmap.Map) (*Resolver, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Resolver{Root: root, Map: m}, nil return &Resolver{
Root: root,
Map: m,
externCache: make(map[string]closure.SourceInput),
}, nil
} }
func (r *Resolver) ResolveRequest(code string, mode OutputMode, excludes []string) (Result, error) { func (r *Resolver) ResolveRequest(code string, mode OutputMode, excludes []string) (Result, error) {
@@ -242,6 +251,55 @@ func (r *Resolver) MergeCSS(files []classmap.Resource) ([]byte, string, error) {
return b.Bytes(), hashList(files, "css"), nil return b.Bytes(), hashList(files, "css"), nil
} }
func (r *Resolver) GetExterns(files []string) ([]closure.SourceInput, error) {
sources := make([]closure.SourceInput, 0, len(files))
for _, f := range files {
src, ok, err := r.getExtern(f)
if err != nil {
return nil, err
}
if !ok {
continue
}
sources = append(sources, src)
}
return sources, nil
}
func (r *Resolver) getExtern(f string) (closure.SourceInput, bool, error) {
// Fast path: read cache.
r.externMu.RLock()
src, ok := r.externCache[f]
r.externMu.RUnlock()
if ok {
return src, true, nil
}
// Slow path: read file.
b, err := os.ReadFile(filepath.Join(r.Root, filepath.FromSlash(f)))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return closure.SourceInput{}, false, nil
}
return closure.SourceInput{}, false, err
}
src = closure.SourceInput{
Name: f,
Source: string(b),
}
// Store cache.
r.externMu.Lock()
r.externCache[f] = src
r.externMu.Unlock()
return src, true, nil
}
func DecodeRequest(code string) ([]string, error) { func DecodeRequest(code string) ([]string, error) {
sep := "/" sep := "/"
decoded := code decoded := code
+1
View File
@@ -0,0 +1 @@
../botanjs/src