]> source.dussan.org Git - jquery.git/commitdiff
Updated build to packer3
authorJörn Zaefferer <joern.zaefferer@gmail.com>
Wed, 25 Apr 2007 21:11:06 +0000 (21:11 +0000)
committerJörn Zaefferer <joern.zaefferer@gmail.com>
Wed, 25 Apr 2007 21:11:06 +0000 (21:11 +0000)
build/build/pack.js
build/js.jar
build/js/Packer.js [new file with mode: 0644]
build/js/Words.js [new file with mode: 0644]
build/js/base2.js [new file with mode: 0644]

index 8c622ec47a8d6b6cf8cd90aabf4f4955ac4a82f7..5706a218747441fa7240252554f2bbc043cf36b5 100644 (file)
@@ -1,5 +1,18 @@
-load("build/js/ParseMaster.js", "build/js/pack.js", "build/js/writeFile.js");
+load("build/js/writeFile.js");
+load("build/js/base2.js");
+load("build/js/Packer.js");
+load("build/js/Words.js");
 
-var out = readFile( arguments[0] );
+// arguments
+var inFile = arguments[0];
+var outFile = arguments[1] || inFile.replace(/\.js$/, "-p.js");
 
-writeFile( arguments[1], pack( out, 62, true, false ) );
+// options
+var base62 = true;
+var shrink = true;
+
+var script = readFile(inFile);
+var packer = new Packer;
+var packedScript = packer.pack(script, base62, shrink);
+
+writeFile(outFile, packedScript);
index 194e5923eea23b45e763f48fae625b84f4e87aaa..2b926505d024e480e00e18ed2dbeb4359591a8ed 100644 (file)
Binary files a/build/js.jar and b/build/js.jar differ
diff --git a/build/js/Packer.js b/build/js/Packer.js
new file mode 100644 (file)
index 0000000..4e907cf
--- /dev/null
@@ -0,0 +1,205 @@
+/*\r
+       Packer version 3.0 (beta 5) - copyright 2004-2007, Dean Edwards\r
+       http://www.opensource.org/licenses/mit-license\r
+*/\r
+\r
+eval(base2.namespace);\r
+\r
+var IGNORE = RegGrp.IGNORE;\r
+var REMOVE = "";\r
+var SPACE = " ";\r
+var WORDS = /\w+/g;\r
+\r
+var Packer = Base.extend({\r
+       minify: function(script) {\r
+               script = script.replace(Packer.CONTINUE, "");\r
+               script = Packer.clean.exec(script);\r
+               script = Packer.whitespace.exec(script);\r
+               script = Packer.clean.exec(script); // seem to grab a few more bytes on the second pass\r
+               return script;\r
+       },\r
+       \r
+       pack: function(script, base62, shrink) {\r
+               script = this.minify(script);\r
+               if (shrink) script = this._shrinkVariables(script);\r
+               if (base62) script = this._base62Encode(script);        \r
+               return script;\r
+       },\r
+       \r
+       _base62Encode: function(script) {\r
+               var words = new Words(script);\r
+               var encode = function(word) {\r
+                       return words.fetch(word).encoded;\r
+               };\r
+               \r
+               /* build the packed script */\r
+               \r
+               var p = this._escape(script.replace(WORDS, encode));            \r
+               var a = Math.min(Math.max(words.count(), 2), 62);               \r
+               var c = words.count();          \r
+               var k = words;\r
+               var e = Packer["ENCODE" + (a > 10 ? a > 36 ? 62 : 36 : 10)];\r
+               var r = a > 10 ? "e(c)" : "c";\r
+               \r
+               // the whole thing\r
+               return format(Packer.UNPACK, p,a,c,k,e,r);\r
+       },\r
+       \r
+       _escape: function(script) {\r
+               // single quotes wrap the final string so escape them\r
+               // also escape new lines required by conditional comments\r
+               return script.replace(/([\\'])/g, "\\$1").replace(/[\r\n]+/g, "\\n");\r
+       },\r
+       \r
+       _shrinkVariables: function(script) {\r
+               // Windows Scripting Host cannot do regexp.test() on global regexps.\r
+               var global = function(regexp) {\r
+                       // This function creates a global version of the passed regexp.\r
+                       return new RegExp(regexp.source, "g");\r
+               };\r
+               \r
+               var data = []; // encoded strings and regular expressions\r
+               var store = function(string) {\r
+                       var replacement = "#" + data.length;\r
+                       data.push(string);\r
+                       return replacement;\r
+               };\r
+               \r
+               // Base52 encoding (a-Z)\r
+               var encode52 = function(c) {\r
+                       return (c < 52 ? '' : arguments.callee(parseInt(c / 52))) +\r
+                               ((c = c % 52) > 25 ? String.fromCharCode(c + 39) : String.fromCharCode(c + 97));\r
+               };\r
+                               \r
+               // identify blocks, particularly identify function blocks (which define scope)\r
+               var BLOCK = /(function\s*[\w$]*\s*\(\s*([^\)]*)\s*\)\s*)?(\{([^{}]*)\})/;\r
+               var VAR_ = /var\s+/g;\r
+               var VAR_NAME = /var\s+[\w$]{2,}/g; // > 1 char\r
+               var COMMA = /\s*,\s*/;\r
+               var blocks = []; // store program blocks (anything between braces {})\r
+               // encoder for program blocks\r
+               var encode = function(block, func, args) {\r
+                       if (func) { // the block is a function block\r
+                       \r
+                               // decode the function block (THIS IS THE IMPORTANT BIT)\r
+                               // We are retrieving all sub-blocks and will re-parse them in light\r
+                               // of newly shrunk variables\r
+                               block = decode(block);\r
+                               \r
+                               // create the list of variable and argument names \r
+                               var vars = match(block, VAR_NAME).join(",").replace(VAR_, "");\r
+                               var ids = Array2.combine(args.split(COMMA).concat(vars.split(COMMA)));\r
+                               \r
+                               // process each identifier\r
+                               var count = 0, shortId;\r
+                               forEach (ids, function(id) {\r
+                                       id = rescape(trim(id));\r
+                                       if (id) {\r
+                                               // find the next free short name (check everything in the current scope)\r
+                                               do shortId = encode52(count++);\r
+                                               while (new RegExp("[^\\w$.]" + shortId + "[^\\w$:]").test(block));\r
+                                               // replace the long name with the short name\r
+                                               var reg = new RegExp("([^\\w$.])" + id + "([^\\w$:])");\r
+                                               while (reg.test(block)) block = block.replace(global(reg), "$1" + shortId + "$2");\r
+                                               var reg = new RegExp("([^{,])" + id + ":", "g");\r
+                                               block = block.replace(reg, "$1" + shortId + ":");\r
+                                       }\r
+                               });\r
+                       }\r
+                       var replacement = "~" + blocks.length;\r
+                       blocks.push(block);\r
+                       return replacement;\r
+               };\r
+               \r
+               // decoder for program blocks\r
+               var ENCODED = /~(\d+)/;\r
+               var decode = function(script) {\r
+                       while (ENCODED.test(script)) {\r
+                               script = script.replace(global(ENCODED), function(match, index) {\r
+                                       return blocks[index];\r
+                               });\r
+                       }\r
+                       return script;\r
+               };\r
+               \r
+               // encode strings and regular expressions\r
+               script = Packer.data.exec(script, store);\r
+               \r
+               // remove closures (this is for base2 namespaces only)\r
+               script = script.replace(/new function\(_\)\s*\{/g, "{;#;");\r
+               \r
+               // encode blocks, as we encode we replace variable and argument names\r
+               while (BLOCK.test(script)) {\r
+                       script = script.replace(global(BLOCK), encode);\r
+               }\r
+               \r
+               // put the blocks back\r
+               script = decode(script);\r
+               \r
+               // put back the closure (for base2 namespaces only)\r
+               script = script.replace(/\{;#;/g, "new function(_){");\r
+               \r
+               // put strings and regular expressions back\r
+               script = script.replace(/#(\d+)/g, function(match, index) {             \r
+                       return data[index];\r
+               });\r
+               \r
+               return script;\r
+       }\r
+}, {\r
+       CONTINUE: /\\\r?\n/g,\r
+       \r
+       ENCODE10: "String",\r
+       ENCODE36: "function(c){return c.toString(a)}",\r
+       ENCODE62: "function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}",\r
+       \r
+       UNPACK: "eval(function(p,a,c,k,e,r){e=%5;if(!''.replace(/^/,String)){while(c--)r[%6]=k[c]" +\r
+               "||%6;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p." +\r
+                       "replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('%1',%2,%3,'%4'.split('|'),0,{}))",\r
+       \r
+       init: function() {\r
+               this.data = reduce(this.data, new RegGrp, function(data, replacement, expression) {\r
+                       data.store(this.javascript.exec(expression), replacement);\r
+                       return data;\r
+               }, this);\r
+               this.clean = this.data.union(this.clean);\r
+               this.whitespace = this.data.union(this.whitespace);\r
+       },\r
+       \r
+       clean: {\r
+               ";;;[^\\n]*": REMOVE, // triple semi-colons treated like line comments\r
+               "\\(\\s*;\\s*;\\s*\\)": "(;;)", // for (;;) loops\r
+               "throw[^};]+[};]": IGNORE, // a safari 1.3 bug\r
+               ";+\\s*([};])": "$1"\r
+       },\r
+       \r
+       data: {\r
+               // strings\r
+               "STRING1": IGNORE,\r
+               'STRING2': IGNORE,\r
+               "CONDITIONAL": IGNORE, // conditional comments\r
+               "(COMMENT1)\\n\\s*(REGEXP)?": "\n$2",\r
+               "(COMMENT2)\\s*(REGEXP)?": " $3",\r
+               "COMMENT1$": REMOVE,\r
+               "([\\[(\\^=,{}:;&|!*?])\\s*(REGEXP)": "$1$2"\r
+       },\r
+       \r
+       javascript: new RegGrp({\r
+               COMMENT1:    /\/\/[^\n]*/.source,\r
+               COMMENT2:    /\/\*[^*]*\*+([^\/][^*]*\*+)*\//.source,\r
+               CONDITIONAL: /\/\*@|@\*\/|\/\/@[^\n]*\n/.source,\r
+               REGEXP:      /\/(\\\/|[^*\/])(\\.|[^\/\n\\])*\//.source,\r
+               STRING1:     /'(\\.|[^'\\])*'/.source,\r
+               STRING2:     /"(\\.|[^"\\])*"/.source\r
+       }),\r
+       \r
+       whitespace: {\r
+               "(\\d)\\s+(\\.\\s*[a-z\\$_\\[(])": "$1 $2", // http://dean.edwards.name/weblog/2007/04/packer3/#comment84066\r
+               "([+-])\\s+([+-])": "$1 $2", // c = a++ +b;\r
+               "\\b\\s+\\$\\s+\\b": " $ ", // var $ in\r
+               "\\$\\s+\\b": "$ ", // object$ in\r
+               "\\b\\s+\\$": " $", // return $object\r
+               "\\b\\s+\\b": SPACE,\r
+               "\\s+": REMOVE\r
+       }\r
+});\r
diff --git a/build/js/Words.js b/build/js/Words.js
new file mode 100644 (file)
index 0000000..264d42f
--- /dev/null
@@ -0,0 +1,62 @@
+\r
+var Words = Collection.extend({\r
+       constructor: function(script) {\r
+               this.base();\r
+               forEach (script.match(WORDS), this.add, this);\r
+               this.encode();\r
+       },\r
+       \r
+       add: function(word) {\r
+               if (!this.exists(word)) this.base(word);\r
+               word = this.fetch(word);\r
+               word.count++;\r
+               return word;\r
+       },\r
+       \r
+       encode: function() {\r
+               // sort by frequency\r
+               this.sort(function(word1, word2) {\r
+                       return word2.count - word1.count;\r
+               });\r
+               \r
+               eval("var a=62,e=" + Packer.ENCODE62);\r
+               var encode = e;         \r
+               var encoded = new Collection; // a dictionary of base62 -> base10\r
+               var count = this.count();\r
+               for (var i = 0; i < count; i++) {\r
+                       encoded.store(encode(i), i);\r
+               }\r
+               \r
+               var empty = function() {return ""};\r
+               var index = 0;\r
+               forEach (this, function(word) {\r
+                       if (encoded.exists(word)) {\r
+                               word.index = encoded.fetch(word);\r
+                               word.toString = empty;\r
+                       } else {\r
+                               while (this.exists(encode(index))) index++;\r
+                               word.index = index++;\r
+                       }\r
+                       word.encoded = encode(word.index);\r
+               }, this);\r
+               \r
+               // sort by encoding\r
+               this.sort(function(word1, word2) {\r
+                       return word1.index - word2.index;\r
+               });\r
+       },\r
+       \r
+       toString: function() {\r
+               return this.values().join("|");\r
+       }\r
+}, {\r
+       Item: {\r
+               constructor: function(word) {\r
+                       this.toString = function() {return word};\r
+               },\r
+               \r
+               count: 0,\r
+               encoded: "",\r
+               index: -1\r
+       }\r
+});\r
diff --git a/build/js/base2.js b/build/js/base2.js
new file mode 100644 (file)
index 0000000..ee57e5d
--- /dev/null
@@ -0,0 +1,972 @@
+// timestamp: Tue, 24 Apr 2007 09:57:15\r
+/*\r
+       base2.js - copyright 2007, Dean Edwards\r
+       http://www.opensource.org/licenses/mit-license\r
+*/\r
+\r
+var base2 = {};\r
+\r
+// You know, writing a javascript library is awfully time consuming.\r
+\r
+new function(_) { ////////////////////  BEGIN: CLOSURE  ////////////////////\r
+\r
+// =========================================================================\r
+// base2/Base.js\r
+// =========================================================================\r
+\r
+// version 1.1\r
+\r
+var Base = function() {\r
+       // call this method from any other method to invoke that method's ancestor\r
+};\r
+\r
+Base.prototype = {     \r
+       extend: function(source) {\r
+               if (arguments.length > 1) { // extending with a name/value pair\r
+                       var ancestor = this[source];\r
+                       var value = arguments[1];\r
+                       if (typeof value == "function" && ancestor && /\bbase\b/.test(value)) {\r
+                               var method = value;                             \r
+                               value = function() { // override\r
+                                       var previous = this.base;\r
+                                       this.base = ancestor;\r
+                                       var returnValue = method.apply(this, arguments);\r
+                                       this.base = previous;\r
+                                       return returnValue;\r
+                               };\r
+                               value.method = method;\r
+                               value.ancestor = ancestor;\r
+                       }\r
+                       this[source] = value;\r
+               } else if (source) { // extending with an object literal\r
+                       var extend = Base.prototype.extend;\r
+                       if (Base._prototyping) {\r
+                               var key, i = 0, members = ["constructor", "toString", "valueOf"];\r
+                               while (key = members[i++]) if (source[key] != Object.prototype[key]) {\r
+                                       extend.call(this, key, source[key]);\r
+                               }\r
+                       } else if (typeof this != "function") {\r
+                               // if the object has a customised extend() method then use it\r
+                               extend = this.extend || extend;\r
+                       }                       \r
+                       // copy each of the source object's properties to this object\r
+                       for (key in source) if (!Object.prototype[key]) {\r
+                               extend.call(this, key, source[key]);\r
+                       }\r
+               }\r
+               return this;\r
+       },\r
+\r
+       base: Base\r
+};\r
+\r
+Base.extend = function(_instance, _static) { // subclass\r
+       var extend = Base.prototype.extend;\r
+       \r
+       // build the prototype\r
+       Base._prototyping = true;\r
+       var proto = new this;\r
+       extend.call(proto, _instance);\r
+       delete Base._prototyping;\r
+       \r
+       // create the wrapper for the constructor function\r
+       var constructor = proto.constructor;\r
+       var klass = proto.constructor = function() {\r
+               if (!Base._prototyping) {\r
+                       if (this._constructing || this.constructor == klass) { // instantiation\r
+                               this._constructing = true;\r
+                               constructor.apply(this, arguments);\r
+                               delete this._constructing;\r
+                       } else { // casting\r
+                               var object = arguments[0];\r
+                               if (object != null) {\r
+                                       (object.extend || extend).call(object, proto);\r
+                               }\r
+                               return object;\r
+                       }\r
+               }\r
+       };\r
+       \r
+       // build the class interface\r
+       for (var i in Base) klass[i] = this[i];\r
+       klass.ancestor = this;\r
+       klass.base = Base.base;\r
+       klass.prototype = proto;\r
+       klass.toString = this.toString;\r
+       extend.call(klass, _static);\r
+       // class initialisation\r
+       if (typeof klass.init == "function") klass.init();\r
+       return klass;\r
+};\r
+\r
+// initialise\r
+Base = Base.extend({\r
+       constructor: function() {\r
+               this.extend(arguments[0]);\r
+       }\r
+}, {\r
+       ancestor: Object,\r
+       base: Base,\r
+       \r
+       implement: function(_interface) {\r
+               if (typeof _interface == "function") {\r
+                       // if it's a function, call it\r
+                       _interface(this.prototype);\r
+               } else {\r
+                       // add the interface using the extend() method\r
+                       this.prototype.extend(_interface);\r
+               }\r
+               return this;\r
+       }\r
+});\r
+\r
+// =========================================================================\r
+// lang/main.js\r
+// =========================================================================\r
+\r
+var Legacy = typeof $Legacy == "undefined" ? {} : $Legacy;\r
+\r
+var K = function(k) {return k};\r
+\r
+var assert = function(condition, message, Err) {\r
+       if (!condition) {\r
+               throw new (Err || Error)(message || "Assertion failed.");\r
+       }\r
+};\r
+\r
+var assertType = function(object, type, message) {\r
+       if (type) {\r
+               var condition = typeof type == "function" ? instanceOf(object, type) : typeof object == type;\r
+               assert(condition, message || "Invalid type.", TypeError);\r
+       }\r
+};\r
+\r
+var format = function(string) {\r
+       // replace %n with arguments[n]\r
+       // e.g. format("%1 %2%3 %2a %1%3", "she", "se", "lls");\r
+       // ==> "she sells sea shells"\r
+       // only supports nine replacements: %1 - %9\r
+       var args = arguments;\r
+       return String(string).replace(/%([1-9])/g, function(match, index) {\r
+               return index < args.length ? args[index] : match;\r
+       });\r
+};\r
+\r
+var $instanceOf = Legacy.instanceOf || new Function("o,k", "return o instanceof k");\r
+var instanceOf = function(object, klass) {\r
+       assertType(klass, "function", "Invalid 'instanceOf' operand.");\r
+       if ($instanceOf(object, klass)) return true;\r
+       // handle exceptions where the target object originates from another frame\r
+       //  this is handy for JSON parsing (amongst other things)\r
+       if (object != null) switch (klass) {\r
+               case Object:\r
+                       return true;\r
+               case Number:\r
+               case Boolean:\r
+               case Function:\r
+               case String:\r
+                       return typeof object == typeof klass.prototype.valueOf();\r
+               case Array:\r
+                       // this is the only troublesome one\r
+                       return !!(object.join && object.splice && !arguments.callee(object, Function));\r
+               case Date:\r
+                       return !!object.getTimezoneOffset;\r
+               case RegExp:\r
+                       return String(object.constructor.prototype) == String(new RegExp);\r
+       }\r
+       return false;\r
+};\r
+       \r
+var match = function(string, expression) {\r
+       // same as String.match() except that this function will return an empty \r
+       // array if there is no match\r
+       return String(string).match(expression) || [];\r
+};\r
+\r
+var RESCAPE = /([\/()[\]{}|*+-.,^$?\\])/g;\r
+var rescape = function(string) {\r
+       // make a string safe for creating a RegExp\r
+       return String(string).replace(RESCAPE, "\\$1");\r
+};\r
+\r
+var $slice = Array.prototype.slice;\r
+var slice = function(object) {\r
+       // slice an array-like object\r
+       return $slice.apply(object, $slice.call(arguments, 1));\r
+};\r
+\r
+var TRIM = /^\s+|\s+$/g;\r
+var trim = function(string) {\r
+       return String(string).replace(TRIM, "");        \r
+};\r
+\r
+// =========================================================================\r
+// lang/extend.js\r
+// =========================================================================\r
+\r
+var base = function(object, args) {\r
+       // invoke the base method with all supplied arguments\r
+       return object.base.apply(object, args);\r
+};\r
+\r
+var extend = function(object) {\r
+       assert(object != Object.prototype, "Object.prototype is verboten!");\r
+       return Base.prototype.extend.apply(object, slice(arguments, 1));\r
+};\r
+\r
+// =========================================================================\r
+// lang/assignID.js\r
+// =========================================================================\r
+\r
+var $ID = 1;\r
+var assignID = function(object) {\r
+       // assign a unique id\r
+       if (!object.base2ID) object.base2ID = "b2_" + $ID++;\r
+       return object.base2ID;\r
+};\r
+\r
+// =========================================================================\r
+// lang/forEach.js\r
+// =========================================================================\r
+\r
+if (typeof StopIteration == "undefined") {\r
+       StopIteration = new Error("StopIteration");\r
+}\r
+\r
+var forEach = function(object, block, context) {\r
+       if (object == null) return;\r
+       if (typeof object == "function") {\r
+               // functions are a special case\r
+               var fn = Function;\r
+       } else if (typeof object.forEach == "function" && object.forEach != arguments.callee) {\r
+               // the object implements a custom forEach method\r
+               object.forEach(block, context);\r
+               return;\r
+       } else if (typeof object.length == "number") {\r
+               // the object is array-like\r
+               forEach.Array(object, block, context);\r
+               return;\r
+       }\r
+       forEach.Function(fn || Object, object, block, context);\r
+};\r
+\r
+// these are the two core enumeration methods. all other forEach methods\r
+//  eventually call one of these two.\r
+\r
+forEach.Array = function(array, block, context) {\r
+       var i, length = array.length; // preserve\r
+       if (typeof array == "string") {\r
+               for (i = 0; i < length; i++) {\r
+                       block.call(context, array.charAt(i), i, array);\r
+               }\r
+       } else {\r
+               for (i = 0; i < length; i++) {\r
+                       block.call(context, array[i], i, array);\r
+               }\r
+       }\r
+};\r
+\r
+forEach.Function = Legacy.forEach || function(fn, object, block, context) {\r
+       // enumerate an object and compare its keys with fn's prototype\r
+       for (var key in object) {\r
+               if (fn.prototype[key] === undefined) {\r
+                       block.call(context, object[key], key, object);\r
+               }\r
+       }\r
+};\r
+\r
+// =========================================================================\r
+// base2/Base/forEach.js\r
+// =========================================================================\r
+\r
+Base.forEach = function(object, block, context) {\r
+       forEach.Function(this, object, block, context);\r
+};\r
+\r
+// =========================================================================\r
+// base2/../Function.js\r
+// =========================================================================\r
+\r
+// some browsers don't define this\r
+\r
+Function.prototype.prototype = {};\r
+\r
+\r
+// =========================================================================\r
+// base2/../String.js\r
+// =========================================================================\r
+\r
+// fix String.replace (Safari/IE5.0)\r
+\r
+if ("".replace(/^/, String)) {\r
+       extend(String.prototype, "replace", function(expression, replacement) {\r
+               if (typeof replacement == "function") { // Safari doesn't like functions\r
+                       if (instanceOf(expression, RegExp)) {\r
+                               var regexp = expression;\r
+                               var global = regexp.global;\r
+                               if (global == null) global = /(g|gi)$/.test(regexp);\r
+                               // we have to convert global RexpExps for exec() to work consistently\r
+                               if (global) regexp = new RegExp(regexp.source); // non-global\r
+                       } else {\r
+                               regexp = new RegExp(rescape(expression));\r
+                       }\r
+                       var match, string = this, result = "";\r
+                       while (string && (match = regexp.exec(string))) {\r
+                               result += string.slice(0, match.index) + replacement.apply(this, match);\r
+                               string = string.slice(match.index + match[0].length);\r
+                               if (!global) break;\r
+                       }\r
+                       return result + string;\r
+               } else {\r
+                       return base(this, arguments);\r
+               }\r
+       });\r
+}\r
+\r
+// =========================================================================\r
+// base2/Abstract.js\r
+// =========================================================================\r
+\r
+var Abstract = Base.extend({\r
+       constructor: function() {\r
+               throw new TypeError("Class cannot be instantiated.");\r
+       }\r
+});\r
+\r
+// =========================================================================\r
+// base2/Module.js\r
+// =========================================================================\r
+\r
+// based on ruby's Module class and Mozilla's Array generics:\r
+//   http://www.ruby-doc.org/core/classes/Module.html\r
+//   http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6#Array_and_String_generics\r
+\r
+// A Module is used as the basis for creating interfaces that can be\r
+// applied to other classes. *All* properties and methods are static.\r
+// When a module is used as a mixin, methods defined on what would normally be\r
+// the instance interface become instance methods of the target object.\r
+\r
+// Modules cannot be instantiated. Static properties and methods are inherited.\r
+\r
+var Module = Abstract.extend(null, {\r
+       extend: function(_interface, _static) {\r
+               // extend a module to create a new module\r
+               var module = this.base();\r
+               // inherit static methods\r
+               forEach (this, function(property, name) {\r
+                       if (!Module[name] && name != "init") {\r
+                               extend(module, name, property);\r
+                       }\r
+               });\r
+               // implement module (instance AND static) methods\r
+               module.implement(_interface);\r
+               // implement static properties and methods\r
+               extend(module, _static);\r
+               // Make the submarine noises Larry!\r
+               if (typeof module.init == "function") module.init();\r
+               return module;\r
+       },\r
+       \r
+       implement: function(_interface) {\r
+               // implement an interface on BOTH the instance and static interfaces\r
+               var module = this;\r
+               if (typeof _interface == "function") {\r
+                       module.base(_interface);\r
+                       forEach (_interface, function(property, name) {\r
+                               if (!Module[name] && name != "init") {\r
+                                       extend(module, name, property);\r
+                               }\r
+                       });\r
+               } else {\r
+                       // create the instance interface\r
+                       Base.forEach (extend({}, _interface), function(property, name) {\r
+                               // instance methods call the equivalent static method\r
+                               if (typeof property == "function") {\r
+                                       property = function() {\r
+                                               base; // force inheritance\r
+                                               return module[name].apply(module, [this].concat(slice(arguments)));\r
+                                       };\r
+                               }\r
+                               if (!Module[name]) extend(this, name, property);\r
+                       }, module.prototype);\r
+                       // add the static interface\r
+                       extend(module, _interface);\r
+               }\r
+               return module;\r
+       }\r
+});\r
+\r
+\r
+// =========================================================================\r
+// base2/Enumerable.js\r
+// =========================================================================\r
+\r
+var Enumerable = Module.extend({\r
+       every: function(object, test, context) {\r
+               var result = true;\r
+               try {\r
+                       this.forEach (object, function(value, key) {\r
+                               result = test.call(context, value, key, object);\r
+                               if (!result) throw StopIteration;\r
+                       });\r
+               } catch (error) {\r
+                       if (error != StopIteration) throw error;\r
+               }\r
+               return !!result; // cast to boolean\r
+       },\r
+       \r
+       filter: function(object, test, context) {\r
+               return this.reduce(object, new Array2, function(result, value, key) {\r
+                       if (test.call(context, value, key, object)) {\r
+                               result[result.length] = value;\r
+                       }\r
+                       return result;\r
+               });\r
+       },\r
+\r
+       invoke: function(object, method) {\r
+               // apply a method to each item in the enumerated object\r
+               var args = slice(arguments, 2);\r
+               return this.map(object, (typeof method == "function") ? function(item) {\r
+                       if (item != null) return method.apply(item, args);\r
+               } : function(item) {\r
+                       if (item != null) return item[method].apply(item, args);\r
+               });\r
+       },\r
+       \r
+       map: function(object, block, context) {\r
+               var result = new Array2;\r
+               this.forEach (object, function(value, key) {\r
+                       result[result.length] = block.call(context, value, key, object);\r
+               });\r
+               return result;\r
+       },\r
+       \r
+       pluck: function(object, key) {\r
+               return this.map(object, function(item) {\r
+                       if (item != null) return item[key];\r
+               });\r
+       },\r
+       \r
+       reduce: function(object, result, block, context) {\r
+               this.forEach (object, function(value, key) {\r
+                       result = block.call(context, result, value, key, object);\r
+               });\r
+               return result;\r
+       },\r
+       \r
+       some: function(object, test, context) {\r
+               return !this.every(object, function(value, key) {\r
+                       return !test.call(context, value, key, object);\r
+               });\r
+       }\r
+}, {\r
+       forEach: forEach\r
+});\r
+\r
+// =========================================================================\r
+// base2/Array2.js\r
+// =========================================================================\r
+\r
+// The IArray module implements all Array methods.\r
+// This module is not public but its methods are accessible through the Array2 object (below). \r
+\r
+var IArray = Module.extend({\r
+       combine: function(keys, values) {\r
+               // combine two arrays to make a hash\r
+               if (!values) values = keys;\r
+               return this.reduce(keys, {}, function(object, key, index) {\r
+                       object[key] = values[index];\r
+                       return object;\r
+               });\r
+       },\r
+       \r
+       copy: function(array) {\r
+               return this.concat(array);\r
+       },\r
+       \r
+       contains: function(array, item) {\r
+               return this.indexOf(array, item) != -1;\r
+       },\r
+       \r
+       forEach: forEach.Array,\r
+       \r
+       indexOf: function(array, item, fromIndex) {\r
+               var length = array.length;\r
+               if (fromIndex == null) {\r
+                       fromIndex = 0;\r
+               } else if (fromIndex < 0) {\r
+                       fromIndex = Math.max(0, length + fromIndex);\r
+               }\r
+               for (var i = fromIndex; i < length; i++) {\r
+                       if (array[i] === item) return i;\r
+               }\r
+               return -1;\r
+       },\r
+       \r
+       insertAt: function(array, item, index) {\r
+               this.splice(array, index, 0, item);\r
+               return item;\r
+       },\r
+       \r
+       insertBefore: function(array, item, before) {\r
+               var index = this.indexOf(array, before);\r
+               if (index == -1) this.push(array, item);\r
+               else this.splice(array, index, 0, item);\r
+               return item;\r
+       },\r
+       \r
+       lastIndexOf: function(array, item, fromIndex) {\r
+               var length = array.length;\r
+               if (fromIndex == null) {\r
+                       fromIndex = length - 1;\r
+               } else if (from < 0) {\r
+                       fromIndex = Math.max(0, length + fromIndex);\r
+               }\r
+               for (var i = fromIndex; i >= 0; i--) {\r
+                       if (array[i] === item) return i;\r
+               }\r
+               return -1;\r
+       },\r
+       \r
+       remove: function(array, item) {\r
+               var index = this.indexOf(array, item);\r
+               if (index != -1) this.removeAt(array, index);\r
+               return item;\r
+       },\r
+       \r
+       removeAt: function(array, index) {\r
+               var item = array[index];\r
+               this.splice(array, index, 1);\r
+               return item;\r
+       }\r
+});\r
+\r
+IArray.prototype.forEach = function(block, context) {\r
+       forEach.Array(this, block, context);\r
+};\r
+\r
+IArray.implement(Enumerable);\r
+\r
+forEach ("concat,join,pop,push,reverse,shift,slice,sort,splice,unshift".split(","), function(name) {\r
+       IArray[name] = function(array) {\r
+               return Array.prototype[name].apply(array, slice(arguments, 1));\r
+       };\r
+});\r
+\r
+// create a faux constructor that augments the built-in Array object\r
+var Array2 = function() {\r
+       return IArray(this.constructor == IArray ? Array.apply(null, arguments) : arguments[0]);\r
+};\r
+// expose IArray.prototype so that it can be extended\r
+Array2.prototype = IArray.prototype;\r
+\r
+forEach (IArray, function(method, name, proto) {\r
+       if (Array[name]) {\r
+               IArray[name] = Array[name];\r
+               delete IArray.prototype[name];\r
+       }\r
+       Array2[name] = IArray[name];\r
+});\r
+\r
+// =========================================================================\r
+// base2/Hash.js\r
+// =========================================================================\r
+\r
+var HASH = "#" + Number(new Date);\r
+var KEYS = HASH + "keys";\r
+var VALUES = HASH + "values";\r
+\r
+var Hash = Base.extend({\r
+       constructor: function(values) {\r
+               this[KEYS] = new Array2;\r
+               this[VALUES] = {};\r
+               this.merge(values);\r
+       },\r
+\r
+       copy: function() {\r
+               var copy = new this.constructor(this);\r
+               Base.forEach (this, function(property, name) {\r
+                       if (typeof property != "function" && name.charAt(0) != "#") {\r
+                               copy[name] = property;\r
+                       }\r
+               });\r
+               return copy;\r
+       },\r
+\r
+       // ancient browsers throw an error when we use "in" as an operator \r
+       //  so we must create the function dynamically\r
+       exists: Legacy.exists || new Function("k", format("return('%1'+k)in this['%2']", HASH, VALUES)),\r
+\r
+       fetch: function(key) {\r
+               return this[VALUES][HASH + key];\r
+       },\r
+\r
+       forEach: function(block, context) {\r
+               forEach (this[KEYS], function(key) {\r
+                       block.call(context, this.fetch(key), key, this);\r
+               }, this);\r
+       },\r
+\r
+       keys: function(index, length) {\r
+               var keys = this[KEYS] || new Array2;\r
+               switch (arguments.length) {\r
+                       case 0: return keys.copy();\r
+                       case 1: return keys[index];\r
+                       default: return keys.slice(index, length);\r
+               }\r
+       },\r
+\r
+       merge: function(values) {\r
+               forEach (arguments, function(values) {\r
+                       forEach (values, function(value, key) {\r
+                               this.store(key, value);\r
+                       }, this);\r
+               }, this);\r
+               return this;\r
+       },\r
+\r
+       remove: function(key) {\r
+               var value = this.fetch(key);\r
+               this[KEYS].remove(String(key));\r
+               delete this[VALUES][HASH + key];\r
+               return value;\r
+       },\r
+\r
+       store: function(key, value) {\r
+               if (arguments.length == 1) value = key;\r
+               // only store the key for a new entry\r
+               if (!this.exists(key)) {\r
+                       this[KEYS].push(String(key));\r
+               }\r
+               // create the new entry (or overwrite the old entry)\r
+               this[VALUES][HASH + key] = value;\r
+               return value;\r
+       },\r
+\r
+       toString: function() {\r
+               return String(this[KEYS]);\r
+       },\r
+\r
+       union: function(values) {\r
+               return this.merge.apply(this.copy(), arguments);\r
+       },\r
+\r
+       values: function(index, length) {\r
+               var values = this.map(K);\r
+               switch (arguments.length) {\r
+                       case 0: return values;\r
+                       case 1: return values[index];\r
+                       default: return values.slice(index, length);\r
+               }\r
+       }\r
+});\r
+\r
+Hash.implement(Enumerable);\r
+\r
+// =========================================================================\r
+// base2/Collection.js\r
+// =========================================================================\r
+\r
+// A Hash that is more array-like (accessible by index).\r
+\r
+// Collection classes have a special (optional) property: Item\r
+// The Item property points to a constructor function.\r
+// Members of the collection must be an instance of Item.\r
+// e.g.\r
+//     var Dates = Collection.extend();                 // create a collection class\r
+//     Dates.Item = Date;                               // only JavaScript Date objects allowed as members\r
+//     var appointments = new Dates();                  // instantiate the class\r
+//     appointments.add(appointmentId, new Date);       // add a date\r
+//     appointments.add(appointmentId, "tomorrow");     // ERROR!\r
+\r
+// The static create() method is responsible for all construction of collection items.\r
+// Instance methods that add new items (add, store, insertAt, replaceAt) pass *all* of their arguments\r
+// to the static create() method. If you want to modify the way collection items are \r
+// created then you only need to override this method for custom collections.\r
+\r
+var Collection = Hash.extend({\r
+       add: function(key, item) {\r
+               // Duplicates not allowed using add().\r
+               //  - but you can still overwrite entries using store()\r
+               assert(!this.exists(key), "Duplicate key.");\r
+               return this.store.apply(this, arguments);\r
+       },\r
+\r
+       count: function() {\r
+               return this[KEYS].length;\r
+       },\r
+\r
+       indexOf: function(key) {\r
+               return this[KEYS].indexOf(String(key));\r
+       },\r
+\r
+       insertAt: function(index, key, item) {\r
+               assert(!this.exists(key), "Duplicate key.");\r
+               this[KEYS].insertAt(index, String(key));\r
+               return this.store.apply(this, slice(arguments, 1));\r
+       },\r
+\r
+       item: function(index) {\r
+               return this.fetch(this[KEYS][index]);\r
+       },\r
+\r
+       removeAt: function(index) {\r
+               return this.remove(this[KEYS][index]);\r
+       },\r
+\r
+       reverse: function() {\r
+               this[KEYS].reverse();\r
+               return this;\r
+       },\r
+\r
+       sort: function(compare) {\r
+               if (compare) {\r
+                       var self = this;\r
+                       this[KEYS].sort(function(key1, key2) {\r
+                               return compare(self.fetch(key1), self.fetch(key2), key1, key2);\r
+                       });\r
+               } else this[KEYS].sort();\r
+               return this;\r
+       },\r
+\r
+       store: function(key, item) {\r
+               if (arguments.length == 1) item = key;\r
+               item = this.constructor.create.apply(this.constructor, arguments);\r
+               return this.base(key, item);\r
+       },\r
+\r
+       storeAt: function(index, item) {\r
+               //-dean: get rid of this?\r
+               assert(index < this.count(), "Index out of bounds.");\r
+               arguments[0] = this[KEYS][index];\r
+               return this.store.apply(this, arguments);\r
+       }\r
+}, {\r
+       Item: null, // if specified, all members of the Collection must be instances of Item\r
+       \r
+       create: function(key, item) {\r
+               if (this.Item && !instanceOf(item, this.Item)) {\r
+                       item = new this.Item(key, item);\r
+               }\r
+               return item;\r
+       },\r
+       \r
+       extend: function(_instance, _static) {\r
+               var klass = this.base(_instance);\r
+               klass.create = this.create;\r
+               extend(klass, _static);\r
+               if (!klass.Item) {\r
+                       klass.Item = this.Item;\r
+               } else if (typeof klass.Item != "function") {\r
+                       klass.Item = (this.Item || Base).extend(klass.Item);\r
+               }\r
+               if (typeof klass.init == "function") klass.init();\r
+               return klass;\r
+       }\r
+});\r
+\r
+// =========================================================================\r
+// base2/RegGrp.js\r
+// =========================================================================\r
+\r
+var RegGrp = Collection.extend({\r
+       constructor: function(values, flags) {\r
+               this.base(values);\r
+               if (typeof flags == "string") {\r
+                       this.global = /g/.test(flags);\r
+                       this.ignoreCase = /i/.test(flags);\r
+               }\r
+       },\r
+\r
+       global: true, // global is the default setting\r
+       ignoreCase: false,\r
+\r
+       exec: function(string, replacement) {\r
+               if (arguments.length == 1) {\r
+                       var keys = this[KEYS];\r
+                       var values = this[VALUES];\r
+                       replacement = function(match) {\r
+                               if (!match) return "";\r
+                               var offset = 1, i = 0;\r
+                               // loop through the values\r
+                               while (match = values[HASH + keys[i++]]) {\r
+                                       // do we have a result?\r
+                                       if (arguments[offset]) {\r
+                                               var replacement = match.replacement;\r
+                                               switch (typeof replacement) {\r
+                                                       case "function":\r
+                                                               return replacement.apply(null, slice(arguments, offset));\r
+                                                       case "number":\r
+                                                               return arguments[offset + replacement];\r
+                                                       default:\r
+                                                               return replacement;\r
+                                               }\r
+                                       // no? then skip over references to sub-expressions\r
+                                       } else offset += match.length + 1;\r
+                               }\r
+                       };\r
+               }\r
+               var flags = (this.global ? "g" : "") + (this.ignoreCase ? "i" : "");\r
+               return String(string).replace(new RegExp(this, flags), replacement);\r
+       },\r
+\r
+       test: function(string) {\r
+               return this.exec(string) != string;\r
+       },\r
+       \r
+       toString: function() {\r
+               var length = 0;\r
+               return "(" + this.map(function(item) {\r
+                       // fix back references\r
+                       var expression = String(item).replace(/\\(\d+)/g, function($, index) {\r
+                               return "\\" + (1 + Number(index) + length);\r
+                       });\r
+                       length += item.length + 1;\r
+                       return expression;\r
+               }).join(")|(") + ")";\r
+       }\r
+}, {\r
+       IGNORE: "$0",\r
+       \r
+       init: function() {\r
+               forEach ("add,exists,fetch,remove,store".split(","), function(name) {\r
+                       extend(this, name, function(expression) {\r
+                               if (instanceOf(expression, RegExp)) {\r
+                                       expression = expression.source;\r
+                               }\r
+                               return base(this, arguments);\r
+                       });\r
+               }, this.prototype);\r
+       }\r
+});\r
+\r
+// =========================================================================\r
+// base2/RegGrp/Item.js\r
+// =========================================================================\r
+\r
+RegGrp.Item = Base.extend({\r
+       constructor: function(expression, replacement) {\r
+               var ESCAPE = /\\./g;\r
+               var STRING = /(['"])\1\+(.*)\+\1\1$/;\r
+       \r
+               expression = instanceOf(expression, RegExp) ? expression.source : String(expression);\r
+               \r
+               if (typeof replacement == "number") replacement = String(replacement);\r
+               else if (replacement == null) replacement = "";\r
+               \r
+               // count the number of sub-expressions\r
+               //  - add one because each pattern is itself a sub-expression\r
+               this.length = match(expression.replace(ESCAPE, "").replace(/\[[^\]]+\]/g, ""), /\(/g).length;\r
+               \r
+               // does the pattern use sub-expressions?\r
+               if (typeof replacement == "string" && /\$(\d+)/.test(replacement)) {\r
+                       // a simple lookup? (e.g. "$2")\r
+                       if (/^\$\d+$/.test(replacement)) {\r
+                               // store the index (used for fast retrieval of matched strings)\r
+                               replacement = parseInt(replacement.slice(1));\r
+                       } else { // a complicated lookup (e.g. "Hello $2 $1")\r
+                               // build a function to do the lookup\r
+                               var i = this.length + 1;\r
+                               var Q = /'/.test(replacement.replace(ESCAPE, "")) ? '"' : "'";\r
+                               replacement = replacement.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\$(\d+)/g, Q +\r
+                                       "+(arguments[$1]||" + Q+Q + ")+" + Q);\r
+                               replacement = new Function("return " + Q + replacement.replace(STRING, "$1") + Q);\r
+                       }\r
+               }\r
+               this.replacement = replacement;\r
+               this.toString = function() {\r
+                       return expression || "";\r
+               };\r
+       },\r
+       \r
+       length: 0,\r
+       replacement: ""\r
+});\r
+\r
+// =========================================================================\r
+// base2/Namespace.js\r
+// =========================================================================\r
+\r
+var Namespace = Base.extend({\r
+       constructor: function(_private, _public) {\r
+               this.extend(_public);\r
+               this.toString = function() {\r
+                       return format("[base2.%1]", this.name);\r
+               };\r
+               \r
+               // initialise\r
+               if (typeof this.init == "function") this.init();\r
+               \r
+               if (this.name != "base2") {\r
+                       this.namespace = format("var %1=base2.%1;", this.name);\r
+               }\r
+               \r
+               var namespace = "var base=" + base + ";";\r
+               var imports = ("base2,lang," + this.imports).split(",");\r
+               _private.imports = Enumerable.reduce(imports, namespace, function(namespace, name) {\r
+                       if (base2[name]) namespace += base2[name].namespace;\r
+                       return namespace;\r
+               });\r
+               \r
+               var namespace = format("base2.%1=%1;", this.name);\r
+               var exports = this.exports.split(",");\r
+               _private.exports = Enumerable.reduce(exports, namespace, function(namespace, name) {\r
+                       if (name) {\r
+                               this.namespace += format("var %2=%1.%2;", this.name, name);\r
+                               namespace += format("if(!%1.%2)%1.%2=%2;base2.%2=%1.%2;", this.name, name);\r
+                       }\r
+                       return namespace;\r
+               }, this);\r
+               \r
+               if (this.name != "base2") {\r
+                       base2.namespace += format("var %1=base2.%1;", this.name);\r
+               }\r
+       },\r
+\r
+       exports: "",\r
+       imports: "",\r
+       namespace: "",\r
+       name: ""\r
+});\r
+\r
+base2 = new Namespace(this, {\r
+       name:    "base2",\r
+       version: "0.8 (alpha)",\r
+       exports: "Base,Abstract,Module,Enumerable,Array2,Hash,Collection,RegGrp,Namespace"\r
+});\r
+\r
+base2.toString = function() {\r
+       return "[base2]";\r
+};\r
+\r
+eval(this.exports);\r
+\r
+// =========================================================================\r
+// base2/lang/namespace.js\r
+// =========================================================================\r
+\r
+var lang = new Namespace(this, {\r
+       name:    "lang",\r
+       version: base2.version,\r
+       exports: "K,assert,assertType,assignID,instanceOf,extend,format,forEach,match,rescape,slice,trim",\r
+       \r
+       init: function() {\r
+               this.extend = extend;\r
+               // add the Enumerable methods to the lang object\r
+               forEach (Enumerable.prototype, function(method, name) {\r
+                       if (!Module[name]) {\r
+                               this[name] = function() {\r
+                                       return Enumerable[name].apply(Enumerable, arguments);\r
+                               };\r
+                               this.exports += "," + name;\r
+                       }\r
+               }, this);\r
+       }\r
+});\r
+\r
+eval(this.exports);\r
+\r
+base2.namespace += lang.namespace;\r
+\r
+}; ////////////////////  END: CLOSURE  /////////////////////////////////////\r