aboutsummaryrefslogtreecommitdiffstats
path: root/infrastructure/ace/bin/make
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xinfrastructure/ace/bin/make339
1 files changed, 339 insertions, 0 deletions
diff --git a/infrastructure/ace/bin/make b/infrastructure/ace/bin/make
new file mode 100755
index 0000000..98a48f4
--- /dev/null
+++ b/infrastructure/ace/bin/make
@@ -0,0 +1,339 @@
+#!/bin/sh
+mkdir -p ../../etherpad/src/etherpad/collab/ace
+mkdir -p ../../etherpad/src/static/js
+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 = "// DO NOT EDIT THIS FILE, edit "+
+ "infrastructure/ace/www/"+fromName+"\n"+code;
+ 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 = "// DO NOT EDIT THIS FILE, edit "+
+ "infrastructure/ace/www/"+fromName+"\n"+code;
+ 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;
+}