diff options
author | Egil Moeller <egil.moller@freecode.no> | 2010-03-21 23:33:06 +0100 |
---|---|---|
committer | Egil Moeller <egil.moller@freecode.no> | 2010-03-21 23:33:06 +0100 |
commit | d56b9b3b82cdebcaeb00eec0fcb4326ad21adaa8 (patch) | |
tree | b1dfe31956f3fc86e3408f1efac5e12acf65b11a /infrastructure/ace/bin/make | |
parent | c1894c8e0a52f4e3d2f89fa92f0066bbf0fcf1b1 (diff) | |
parent | 103d4926ae6c61824dc0b48be7bf66f08830ed47 (diff) | |
download | etherpad-d56b9b3b82cdebcaeb00eec0fcb4326ad21adaa8.tar.gz etherpad-d56b9b3b82cdebcaeb00eec0fcb4326ad21adaa8.tar.xz etherpad-d56b9b3b82cdebcaeb00eec0fcb4326ad21adaa8.zip |
Merge branch 'master' of git@github.com:ether/pad
Diffstat (limited to 'infrastructure/ace/bin/make')
-rwxr-xr-x | infrastructure/ace/bin/make | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/infrastructure/ace/bin/make b/infrastructure/ace/bin/make new file mode 100755 index 0000000..dad11ff --- /dev/null +++ b/infrastructure/ace/bin/make @@ -0,0 +1,337 @@ +#!/bin/sh +exec scala -classpath lib/yuicompressor-2.4-appjet.jar:lib/rhino-js-1.7r1.jar $0 $@ +!# + +import java.io._; + +def superpack(input: String): String = { + // this function is self-contained; takes a string, returns an expression + // that evaluates to that string + // XXX (This compresses well but decompression is too slow) + + // constraints on special chars: + // - this string must be able to go in a character class + // - each char must be able to go in single quotes + val specialChars = "-~@%$#*^_`()|abcdefghijklmnopqrstuvwxyz=!+,.;:?{}"; + val specialCharsSet:Set[Char] = Set(specialChars:_*); + def containsSpecialChar(str: String) = str.exists(specialCharsSet.contains(_)); + + val toks:Array[String] = ( + "@|[a-zA-Z0-9]+|[^@a-zA-Z0-9]{1,3}").r.findAllIn(input).collect.toArray; + + val stringCounts = { + val m = new scala.collection.mutable.HashMap[String,Int]; + def incrementCount(s: String) = { m(s) = m.getOrElse(s, 0) + 1; } + for(s <- toks) incrementCount(s); + m; + } + + val estimatedSavings = scala.util.Sorting.stableSort( + for((s,n) <- stringCounts.toArray; savings = s.length*n + if (savings > 8 || containsSpecialChar(s))) + yield (s,n,savings), + (x:(String,Int,Int))=> -x._3); + + def strLast(str: String, n: Int) = str.substring(str.length - n, str.length); + // order of encodeNames is very important! + val encodeNames = for(n <- 0 until (36*36); c <- specialChars) yield c.toString+strLast("0"+Integer.toString(n, 36).toUpperCase, 2); + + val thingsToReplace:Seq[String] = estimatedSavings.map(_._1); + assert(encodeNames.length >= thingsToReplace.length); + + val replacements = Map(thingsToReplace.elements.zipWithIndex.map({ + case (str, i) => (str, encodeNames(i)); + }).collect:_*); + def encode(tk: String) = if (replacements.contains(tk)) replacements(tk) else tk; + + val afterReplace = toks.map(encode(_)).mkString.replaceAll( + "(["+specialChars+"])(?=..[^0-9A-Z])(00|0)", "$1"); + + def makeSingleQuotedContents(str: String): String = { + str.replace("\\", "\\\\").replace("'", "\\'").replace("<", "\\x3c").replace("\n", "\\n"). + replace("\r", "\\n").replace("\t", "\\t"); + } + + val expansionMap = new scala.collection.mutable.HashMap[Char,scala.collection.mutable.ArrayBuffer[String]]; + for(i <- 0 until thingsToReplace.length; sc = encodeNames(i).charAt(0); + e = thingsToReplace(i)) { + expansionMap.getOrElseUpdate(sc, new scala.collection.mutable.ArrayBuffer[String]) += + (if (e == "@") "" else e); + } + val expansionMapLiteral = "{"+(for((sc,strs) <- expansionMap) yield { + "'"+sc+"':'"+makeSingleQuotedContents(strs.mkString("@"))+"'"; + }).mkString(",")+"}"; + + val expr = ("(function(m){m="+expansionMapLiteral+ + ";for(var k in m){if(m.hasOwnProperty(k))m[k]=m[k].split('@')};return '"+ + makeSingleQuotedContents(afterReplace)+ + "'.replace(/(["+specialChars+ + "])([0-9A-Z]{0,2})/g,function(a,b,c){return m[b][parseInt(c||'0',36)]||'@'})}())"); + /*val expr = ("(function(m){m="+expansionMapLiteral+ + ";for(var k in m){if(m.hasOwnProperty(k))m[k]=m[k].split('@')};"+ + "var result=[];var i=0;var s='"+makeSingleQuotedContents(afterReplace)+ + "';var len=s.length;while (i<len) {var x=s.charAt(i); var L=m[x],a = s.charAt(i+1),b = s.charAt(i+2);if (L) { var c;if (!(a >= 'A' && a <= 'Z' || a >= '0' && a <= '9')) {c=L[0];i++} else if (!(b >= 'A' && b <= 'Z' || b >= '0' && b <= '9')) {c = L[parseInt(a,36)]; i+=2} else {c = L[parseInt(a+b,36)]; i+=3}; result.push(c||'@'); } else {result.push(x); i++} }; return result.join(''); }())");*/ + + def evaluateString(js: String): String = { + import org.mozilla.javascript._; + ContextFactory.getGlobal.call(new ContextAction { + def run(cx: Context) = { + val scope = cx.initStandardObjects; + cx.evaluateString(scope, js, "<cmd>", 1, null) } }).asInstanceOf[String]; + } + + def putFile(str: String, path: String): Unit = { + import java.io._; + val writer = new FileWriter(path); + writer.write(str); + writer.close; + } + + val exprOut = evaluateString(expr); + if (exprOut != input) { + putFile(input, "/tmp/superpack.input"); + putFile(expr, "/tmp/superpack.expr"); + putFile(exprOut, "/tmp/superpack.output"); + error("Superpacked string does not evaluate to original string; check /tmp/superpack.*"); + } + + val singleLiteral = "'"+makeSingleQuotedContents(input)+"'"; + if (singleLiteral.length < expr.length) { + singleLiteral; + } + else { + expr; + } +} + +def doMake { + + lazy val isEtherPad = (args.length >= 2 && args(1) == "etherpad"); + lazy val isNoHelma = (args.length >= 2 && args(1) == "nohelma"); + + def getFile(path:String): String = { + val builder = new StringBuilder(1000); + val reader = new BufferedReader(new FileReader(path)); + val buf = new Array[Char](1024); + var numRead = 0; + while({ numRead = reader.read(buf); numRead } != -1) { + builder.append(buf, 0, numRead); + } + reader.close; + return builder.toString; + } + + def putFile(str: String, path: String): Unit = { + val writer = new FileWriter(path); + writer.write(str); + writer.close; + } + + def writeToString(func:(Writer=>Unit)): String = { + val writer = new StringWriter; + func(writer); + return writer.toString; + } + + def compressJS(code: String, wrap: Boolean): String = { + import yuicompressor.org.mozilla.javascript.{ErrorReporter, EvaluatorException}; + object MyErrorReporter extends ErrorReporter { + def warning(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) { + if (message startsWith "Try to use a single 'var' statement per scope.") return; + if (line < 0) System.err.println("\n[WARNING] " + message); + else System.err.println("\n[WARNING] " + line + ':' + lineOffset + ':' + message); + } + def error(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int) { + if (line < 0) System.err.println("\n[ERROR] " + message); + else System.err.println("\n[ERROR] " + line + ':' + lineOffset + ':' + message); + } + def runtimeError(message:String, sourceName:String, line:Int, lineSource:String, lineOffset:Int): EvaluatorException = { + error(message, sourceName, line, lineSource, lineOffset); + return new EvaluatorException(message); + } + } + + val munge = true; + val verbose = false; + val optimize = true; + val compressor = new com.yahoo.platform.yui.compressor.JavaScriptCompressor(new StringReader(code), MyErrorReporter); + return writeToString(compressor.compress(_, if (wrap) 100 else -1, munge, verbose, true, !optimize)); + } + + def compressCSS(code: String, wrap: Boolean): String = { + val compressor = new com.yahoo.platform.yui.compressor.CssCompressor(new StringReader(code)); + return writeToString(compressor.compress(_, if (wrap) 100 else -1)); + } + + import java.util.regex.{Pattern, Matcher, MatchResult}; + + def stringReplace(orig: String, regex: String, groupReferences:Boolean, func:(MatchResult=>String)): String = { + val buf = new StringBuffer; + val m = Pattern.compile(regex).matcher(orig); + while (m.find) { + var str = func(m); + if (! groupReferences) { + str = str.replace("\\", "\\\\").replace("$", "\\$"); + } + m.appendReplacement(buf, str); + } + m.appendTail(buf); + return buf.toString; + } + + def stringToExpression(str: String): String = { + var contents = str.replace("\\", "\\\\").replace("'", "\\'").replace("<", "\\x3c").replace("\n", "\\n"). + replace("\r", "\\n").replace("\t", "\\t"); + contents = contents.replace("\\/", "\\\\x2f"); // for Norton Internet Security + val result = "'"+contents+"'"; + result; + } + + val srcDir = "www"; + val destDir = "build"; + var code = getFile(srcDir+"/ace2_outer.js"); + + val useCompression = true; //if (isEtherPad) false else true; + + code = stringReplace(code, "\\$\\$INCLUDE_([A-Z_]+)\\([\"']([^\"']+)[\"']\\)", false, (m:MatchResult) => { + val includeType = m.group(1); + val paths = m.group(2); + val pathsArray = paths.replaceAll("""/\*.*?\*/""", "").split(" +").filter(_.length > 0); + def getSubcode = pathsArray.map(p => getFile(srcDir+"/"+p)).mkString("\n"); + val doPack = (stringToExpression _); + includeType match { + case "JS" => { + var subcode = getSubcode; + subcode = subcode.replaceAll("var DEBUG=true;//\\$\\$[^\n\r]*", "var DEBUG=false;"); + if (useCompression) subcode = compressJS(subcode, true); + "('\\x3cscript type=\"text/javascript\">//<!--\\n'+" + doPack(subcode) + + "+'//-->\\n</script>')"; + } + case "CSS" => { + var subcode = getSubcode; + if (useCompression) subcode = compressCSS(subcode, false); + "('<style type=\"text/css\">'+" + doPack(subcode) + "+'</style>')"; + } + case "JS_Q" => { + var subcode = getSubcode + subcode = subcode.replaceAll("var DEBUG=true;//\\$\\$[^\n\r]*", "var DEBUG=false;"); + if (useCompression) subcode = compressJS(subcode, true); + "('(\\'\\\\x3cscript type=\"text/javascript\">//<!--\\\\n\\'+'+" + + doPack(stringToExpression(subcode)) + + "+'+\\'//-->\\\\n\\\\x3c/script>\\')')"; + } + case "CSS_Q" => { + var subcode = getSubcode; + if (useCompression) subcode = compressCSS(subcode, false); + "('(\\'<style type=\"text/css\">\\'+'+" + doPack(stringToExpression(subcode)) + + "+'+\\'\\\\x3c/style>\\')')"; + } + case ("JS_DEV" | "CSS_DEV") => "''"; + case ("JS_Q_DEV" | "CSS_Q_DEV") => "'\\'\\''"; + //case _ => "$$INCLUDE_"+includeType+"(\"../www/"+path+"\")"; + } + }); + + if (useCompression) code = compressJS(code, true); + + putFile(code, destDir+"/ace2bare.js"); + + //var wrapper = getFile(srcDir+"/ace2_wrapper.js"); + //if (useCompression) wrapper = compressJS(wrapper, true); + putFile(/*wrapper+"\n"+*/code, destDir+"/ace2.js"); + + var index = getFile(srcDir+"/index.html"); + index = index.replaceAll("<!--\\s*DEBUG\\s*-->\\s*([\\s\\S]+?)\\s*<!--\\s*/DEBUG\\s*-->", ""); + index = index.replaceAll("<!--\\s*PROD:\\s*([\\s\\S]+?)\\s*-->", "$1"); + putFile(index, destDir+"/index.html"); + + putFile(getFile(srcDir+"/testcode.js"), destDir+"/testcode.js"); + + def copyFile(fromFile: String, toFile: String) { + if (0 != Runtime.getRuntime.exec("cp "+fromFile+" "+toFile).waitFor) { + printf("copy failed (%s -> %s).\n", fromFile, toFile); + } + } + + def replaceFirstLine(txt: String, newFirstLine: String): String = { + var newlinePos = txt.indexOf('\n'); + newFirstLine + txt.substring(newlinePos); + } + + if (isEtherPad) { + copyFile("build/ace2.js", "../../etherpad/src/static/js/ace.js"); + + def copyFileToEtherpad(fromName: String, toName: String) { + var code = getFile(srcDir+"/"+fromName); + code = replaceFirstLine(code, "// DO NOT EDIT THIS FILE, edit "+ + "infrastructure/ace/www/"+fromName); + code = code.replaceAll("""(?<=\n)\s*//\s*%APPJET%:\s*""", ""); + putFile(code, "../../etherpad/src/etherpad/collab/ace/"+toName); + } + def copyFileToClientSide(fromName: String, toName: String) { + var code = getFile(srcDir+"/"+fromName); + code = replaceFirstLine(code, "// DO NOT EDIT THIS FILE, edit "+ + "infrastructure/ace/www/"+fromName); + code = code.replaceAll("""(?<=\n)\s*//\s*%APPJET%:.*?\n""", ""); + code = code.replaceAll("""(?<=\n)\s*//\s*%CLIENT FILE ENDS HERE%[\s\S]*""", + ""); + putFile(code, "../../etherpad/src/static/js/"+toName); + } + + copyFileToEtherpad("easy_sync.js", "easysync1.js"); + copyFileToEtherpad("easysync2.js", "easysync2.js"); + copyFileToEtherpad("contentcollector.js", "contentcollector.js"); + copyFileToEtherpad("easysync2_tests.js", "easysync2_tests.js"); + copyFileToClientSide("colorutils.js", "colorutils.js"); + copyFileToClientSide("easysync2.js", "easysync2_client.js"); + copyFileToEtherpad("linestylefilter.js", "linestylefilter.js"); + copyFileToClientSide("linestylefilter.js", "linestylefilter_client.js"); + copyFileToEtherpad("domline.js", "domline.js"); + copyFileToClientSide("domline.js", "domline_client.js"); + copyFileToClientSide("cssmanager.js", "cssmanager_client.js"); + } + /*else if (! isNoHelma) { + copyFile("build/ace2.js", "../helma_apps/appjet/protectedStatic/js/ace.js"); + }*/ +} + +def remakeLoop { + + def getStamp: Long = { + return (new java.io.File("www").listFiles. + filter(! _.getName.endsWith("~")). + filter(! _.getName.endsWith("#")). + filter(! _.getName.startsWith(".")).map(_.lastModified). + reduceLeft(Math.max(_:Long,_:Long))); + } + + var madeStamp:Long = 0; + var errorStamp:Long = 0; + while (true) { + Thread.sleep(500); + val s = getStamp; + if (s > madeStamp && s != errorStamp) { + Thread.sleep(1000); + if (getStamp == s) { + madeStamp = s; + print("Remaking... "); + try { + doMake; + println("OK"); + } + catch { case e => { + println("ERROR"); + errorStamp = s; + } } + } + } + } + +} + +if (args.length >= 1 && args(0) == "auto") { + remakeLoop; +} +else { + doMake; +} |