SGN::Test::WWW::Mechanize should use the SGN_TEST_SERVER env var
[sgn.git] / js / MochiKit / Base.js
blob67f44990f6f9f57c493f2dbebb58bcdc79484686
1 /***
3 MochiKit.Base 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2005 Bob Ippolito.  All rights Reserved.
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide("MochiKit.Base");
14 if (typeof(MochiKit) == 'undefined') {
15     MochiKit = {};
17 if (typeof(MochiKit.Base) == 'undefined') {
18     MochiKit.Base = {};
20 if (typeof(MochiKit.__export__) == "undefined") {
21     MochiKit.__export__ = (MochiKit.__compat__  ||
22         (typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
23     );
26 MochiKit.Base.VERSION = "1.4";
27 MochiKit.Base.NAME = "MochiKit.Base";
28 /** @id MochiKit.Base.update */
29 MochiKit.Base.update = function (self, obj/*, ... */) {
30     if (self === null) {
31         self = {};
32     }
33     for (var i = 1; i < arguments.length; i++) {
34         var o = arguments[i];
35         if (typeof(o) != 'undefined' && o !== null) {
36             for (var k in o) {
37                 self[k] = o[k];
38             }
39         }
40     }
41     return self;
44 MochiKit.Base.update(MochiKit.Base, {
45     __repr__: function () {
46         return "[" + this.NAME + " " + this.VERSION + "]";
47     },
49     toString: function () {
50         return this.__repr__();
51     },
53     /** @id MochiKit.Base.camelize */
54     camelize: function (selector) {
55         /* from dojo.style.toCamelCase */
56         var arr = selector.split('-');
57         var cc = arr[0];
58         for (var i = 1; i < arr.length; i++) {
59             cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1);
60         }
61         return cc;
62     },
64     /** @id MochiKit.Base.counter */
65     counter: function (n/* = 1 */) {
66         if (arguments.length === 0) {
67             n = 1;
68         }
69         return function () {
70             return n++;
71         };
72     },
74     /** @id MochiKit.Base.clone */
75     clone: function (obj) {
76         var me = arguments.callee;
77         if (arguments.length == 1) {
78             me.prototype = obj;
79             return new me();
80         }
81     },
83     _flattenArray: function (res, lst) {
84         for (var i = 0; i < lst.length; i++) {
85             var o = lst[i];
86             if (o instanceof Array) {
87                 arguments.callee(res, o);
88             } else {
89                 res.push(o);
90             }
91         }
92         return res;
93     },
95     /** @id MochiKit.Base.flattenArray */
96     flattenArray: function (lst) {
97         return MochiKit.Base._flattenArray([], lst);
98     },
100     /** @id MochiKit.Base.flattenArguments */
101     flattenArguments: function (lst/* ...*/) {
102         var res = [];
103         var m = MochiKit.Base;
104         var args = m.extend(null, arguments);
105         while (args.length) {
106             var o = args.shift();
107             if (o && typeof(o) == "object" && typeof(o.length) == "number") {
108                 for (var i = o.length - 1; i >= 0; i--) {
109                     args.unshift(o[i]);
110                 }
111             } else {
112                 res.push(o);
113             }
114         }
115         return res;
116     },
118     /** @id MochiKit.Base.extend */
119     extend: function (self, obj, /* optional */skip) {
120         // Extend an array with an array-like object starting
121         // from the skip index
122         if (!skip) {
123             skip = 0;
124         }
125         if (obj) {
126             // allow iterable fall-through, but skip the full isArrayLike
127             // check for speed, this is called often.
128             var l = obj.length;
129             if (typeof(l) != 'number' /* !isArrayLike(obj) */) {
130                 if (typeof(MochiKit.Iter) != "undefined") {
131                     obj = MochiKit.Iter.list(obj);
132                     l = obj.length;
133                 } else {
134                     throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
135                 }
136             }
137             if (!self) {
138                 self = [];
139             }
140             for (var i = skip; i < l; i++) {
141                 self.push(obj[i]);
142             }
143         }
144         // This mutates, but it's convenient to return because
145         // it's often used like a constructor when turning some
146         // ghetto array-like to a real array
147         return self;
148     },
151     /** @id MochiKit.Base.updatetree */
152     updatetree: function (self, obj/*, ...*/) {
153         if (self === null) {
154             self = {};
155         }
156         for (var i = 1; i < arguments.length; i++) {
157             var o = arguments[i];
158             if (typeof(o) != 'undefined' && o !== null) {
159                 for (var k in o) {
160                     var v = o[k];
161                     if (typeof(self[k]) == 'object' && typeof(v) == 'object') {
162                         arguments.callee(self[k], v);
163                     } else {
164                         self[k] = v;
165                     }
166                 }
167             }
168         }
169         return self;
170     },
172     /** @id MochiKit.Base.setdefault */
173     setdefault: function (self, obj/*, ...*/) {
174         if (self === null) {
175             self = {};
176         }
177         for (var i = 1; i < arguments.length; i++) {
178             var o = arguments[i];
179             for (var k in o) {
180                 if (!(k in self)) {
181                     self[k] = o[k];
182                 }
183             }
184         }
185         return self;
186     },
188     /** @id MochiKit.Base.keys */
189     keys: function (obj) {
190         var rval = [];
191         for (var prop in obj) {
192             rval.push(prop);
193         }
194         return rval;
195     },
197     /** @id MochiKit.Base.values */
198     values: function (obj) {
199         var rval = [];
200         for (var prop in obj) {
201             rval.push(obj[prop]);
202         }
203         return rval;
204     },
206      /** @id MochiKit.Base.items */
207     items: function (obj) {
208         var rval = [];
209         var e;
210         for (var prop in obj) {
211             var v;
212             try {
213                 v = obj[prop];
214             } catch (e) {
215                 continue;
216             }
217             rval.push([prop, v]);
218         }
219         return rval;
220     },
223     _newNamedError: function (module, name, func) {
224         func.prototype = new MochiKit.Base.NamedError(module.NAME + "." + name);
225         module[name] = func;
226     },
229     /** @id MochiKit.Base.operator */
230     operator: {
231         // unary logic operators
232         /** @id MochiKit.Base.truth */
233         truth: function (a) { return !!a; },
234         /** @id MochiKit.Base.lognot */
235         lognot: function (a) { return !a; },
236         /** @id MochiKit.Base.identity */
237         identity: function (a) { return a; },
239         // bitwise unary operators
240         /** @id MochiKit.Base.not */
241         not: function (a) { return ~a; },
242         /** @id MochiKit.Base.neg */
243         neg: function (a) { return -a; },
245         // binary operators
246         /** @id MochiKit.Base.add */
247         add: function (a, b) { return a + b; },
248         /** @id MochiKit.Base.sub */
249         sub: function (a, b) { return a - b; },
250         /** @id MochiKit.Base.div */
251         div: function (a, b) { return a / b; },
252         /** @id MochiKit.Base.mod */
253         mod: function (a, b) { return a % b; },
254         /** @id MochiKit.Base.mul */
255         mul: function (a, b) { return a * b; },
257         // bitwise binary operators
258         /** @id MochiKit.Base.and */
259         and: function (a, b) { return a & b; },
260         /** @id MochiKit.Base.or */
261         or: function (a, b) { return a | b; },
262         /** @id MochiKit.Base.xor */
263         xor: function (a, b) { return a ^ b; },
264         /** @id MochiKit.Base.lshift */
265         lshift: function (a, b) { return a << b; },
266         /** @id MochiKit.Base.rshift */
267         rshift: function (a, b) { return a >> b; },
268         /** @id MochiKit.Base.zrshift */
269         zrshift: function (a, b) { return a >>> b; },
271         // near-worthless built-in comparators
272         /** @id MochiKit.Base.eq */
273         eq: function (a, b) { return a == b; },
274         /** @id MochiKit.Base.ne */
275         ne: function (a, b) { return a != b; },
276         /** @id MochiKit.Base.gt */
277         gt: function (a, b) { return a > b; },
278         /** @id MochiKit.Base.ge */
279         ge: function (a, b) { return a >= b; },
280         /** @id MochiKit.Base.lt */
281         lt: function (a, b) { return a < b; },
282         /** @id MochiKit.Base.le */
283         le: function (a, b) { return a <= b; },
285         // strict built-in comparators
286         seq: function (a, b) { return a === b; },
287         sne: function (a, b) { return a !== b; },
289         // compare comparators
290         /** @id MochiKit.Base.ceq */
291         ceq: function (a, b) { return MochiKit.Base.compare(a, b) === 0; },
292         /** @id MochiKit.Base.cne */
293         cne: function (a, b) { return MochiKit.Base.compare(a, b) !== 0; },
294         /** @id MochiKit.Base.cgt */
295         cgt: function (a, b) { return MochiKit.Base.compare(a, b) == 1; },
296         /** @id MochiKit.Base.cge */
297         cge: function (a, b) { return MochiKit.Base.compare(a, b) != -1; },
298         /** @id MochiKit.Base.clt */
299         clt: function (a, b) { return MochiKit.Base.compare(a, b) == -1; },
300         /** @id MochiKit.Base.cle */
301         cle: function (a, b) { return MochiKit.Base.compare(a, b) != 1; },
303         // binary logical operators
304         /** @id MochiKit.Base.logand */
305         logand: function (a, b) { return a && b; },
306         /** @id MochiKit.Base.logor */
307         logor: function (a, b) { return a || b; },
308         /** @id MochiKit.Base.contains */
309         contains: function (a, b) { return b in a; }
310     },
312     /** @id MochiKit.Base.forwardCall */
313     forwardCall: function (func) {
314         return function () {
315             return this[func].apply(this, arguments);
316         };
317     },
319     /** @id MochiKit.Base.itemgetter */
320     itemgetter: function (func) {
321         return function (arg) {
322             return arg[func];
323         };
324     },
326     /** @id MochiKit.Base.typeMatcher */
327     typeMatcher: function (/* typ */) {
328         var types = {};
329         for (var i = 0; i < arguments.length; i++) {
330             var typ = arguments[i];
331             types[typ] = typ;
332         }
333         return function () {
334             for (var i = 0; i < arguments.length; i++) {
335                 if (!(typeof(arguments[i]) in types)) {
336                     return false;
337                 }
338             }
339             return true;
340         };
341     },
343     /** @id MochiKit.Base.isNull */
344     isNull: function (/* ... */) {
345         for (var i = 0; i < arguments.length; i++) {
346             if (arguments[i] !== null) {
347                 return false;
348             }
349         }
350         return true;
351     },
353     /** @id MochiKit.Base.isUndefinedOrNull */
354     isUndefinedOrNull: function (/* ... */) {
355         for (var i = 0; i < arguments.length; i++) {
356             var o = arguments[i];
357             if (!(typeof(o) == 'undefined' || o === null)) {
358                 return false;
359             }
360         }
361         return true;
362     },
364     /** @id MochiKit.Base.isEmpty */
365     isEmpty: function (obj) {
366         return !MochiKit.Base.isNotEmpty.apply(this, arguments);
367     },
369     /** @id MochiKit.Base.isNotEmpty */
370     isNotEmpty: function (obj) {
371         for (var i = 0; i < arguments.length; i++) {
372             var o = arguments[i];
373             if (!(o && o.length)) {
374                 return false;
375             }
376         }
377         return true;
378     },
380     /** @id MochiKit.Base.isArrayLike */
381     isArrayLike: function () {
382         for (var i = 0; i < arguments.length; i++) {
383             var o = arguments[i];
384             var typ = typeof(o);
385             if (
386                 (typ != 'object' && !(typ == 'function' && typeof(o.item) == 'function')) ||
387                 o === null ||
388                 typeof(o.length) != 'number' ||
389                 o.nodeType === 3
390             ) {
391                 return false;
392             }
393         }
394         return true;
395     },
397     /** @id MochiKit.Base.isDateLike */
398     isDateLike: function () {
399         for (var i = 0; i < arguments.length; i++) {
400             var o = arguments[i];
401             if (typeof(o) != "object" || o === null
402                     || typeof(o.getTime) != 'function') {
403                 return false;
404             }
405         }
406         return true;
407     },
410     /** @id MochiKit.Base.xmap */
411     xmap: function (fn/*, obj... */) {
412         if (fn === null) {
413             return MochiKit.Base.extend(null, arguments, 1);
414         }
415         var rval = [];
416         for (var i = 1; i < arguments.length; i++) {
417             rval.push(fn(arguments[i]));
418         }
419         return rval;
420     },
422     /** @id MochiKit.Base.map */
423     map: function (fn, lst/*, lst... */) {
424         var m = MochiKit.Base;
425         var itr = MochiKit.Iter;
426         var isArrayLike = m.isArrayLike;
427         if (arguments.length <= 2) {
428             // allow an iterable to be passed
429             if (!isArrayLike(lst)) {
430                 if (itr) {
431                     // fast path for map(null, iterable)
432                     lst = itr.list(lst);
433                     if (fn === null) {
434                         return lst;
435                     }
436                 } else {
437                     throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
438                 }
439             }
440             // fast path for map(null, lst)
441             if (fn === null) {
442                 return m.extend(null, lst);
443             }
444             // disabled fast path for map(fn, lst)
445             /*
446             if (false && typeof(Array.prototype.map) == 'function') {
447                 // Mozilla fast-path
448                 return Array.prototype.map.call(lst, fn);
449             }
450             */
451             var rval = [];
452             for (var i = 0; i < lst.length; i++) {
453                 rval.push(fn(lst[i]));
454             }
455             return rval;
456         } else {
457             // default for map(null, ...) is zip(...)
458             if (fn === null) {
459                 fn = Array;
460             }
461             var length = null;
462             for (i = 1; i < arguments.length; i++) {
463                 // allow iterables to be passed
464                 if (!isArrayLike(arguments[i])) {
465                     if (itr) {
466                         return itr.list(itr.imap.apply(null, arguments));
467                     } else {
468                         throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
469                     }
470                 }
471                 // find the minimum length
472                 var l = arguments[i].length;
473                 if (length === null || length > l) {
474                     length = l;
475                 }
476             }
477             rval = [];
478             for (i = 0; i < length; i++) {
479                 var args = [];
480                 for (var j = 1; j < arguments.length; j++) {
481                     args.push(arguments[j][i]);
482                 }
483                 rval.push(fn.apply(this, args));
484             }
485             return rval;
486         }
487     },
489     /** @id MochiKit.Base.xfilter */
490     xfilter: function (fn/*, obj... */) {
491         var rval = [];
492         if (fn === null) {
493             fn = MochiKit.Base.operator.truth;
494         }
495         for (var i = 1; i < arguments.length; i++) {
496             var o = arguments[i];
497             if (fn(o)) {
498                 rval.push(o);
499             }
500         }
501         return rval;
502     },
504     /** @id MochiKit.Base.filter */
505     filter: function (fn, lst, self) {
506         var rval = [];
507         // allow an iterable to be passed
508         var m = MochiKit.Base;
509         if (!m.isArrayLike(lst)) {
510             if (MochiKit.Iter) {
511                 lst = MochiKit.Iter.list(lst);
512             } else {
513                 throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
514             }
515         }
516         if (fn === null) {
517             fn = m.operator.truth;
518         }
519         if (typeof(Array.prototype.filter) == 'function') {
520             // Mozilla fast-path
521             return Array.prototype.filter.call(lst, fn, self);
522         } else if (typeof(self) == 'undefined' || self === null) {
523             for (var i = 0; i < lst.length; i++) {
524                 var o = lst[i];
525                 if (fn(o)) {
526                     rval.push(o);
527                 }
528             }
529         } else {
530             for (i = 0; i < lst.length; i++) {
531                 o = lst[i];
532                 if (fn.call(self, o)) {
533                     rval.push(o);
534                 }
535             }
536         }
537         return rval;
538     },
541     _wrapDumbFunction: function (func) {
542         return function () {
543             // fast path!
544             switch (arguments.length) {
545                 case 0: return func();
546                 case 1: return func(arguments[0]);
547                 case 2: return func(arguments[0], arguments[1]);
548                 case 3: return func(arguments[0], arguments[1], arguments[2]);
549             }
550             var args = [];
551             for (var i = 0; i < arguments.length; i++) {
552                 args.push("arguments[" + i + "]");
553             }
554             return eval("(func(" + args.join(",") + "))");
555         };
556     },
558     /** @id MochiKit.Base.methodcaller */
559     methodcaller: function (func/*, args... */) {
560         var args = MochiKit.Base.extend(null, arguments, 1);
561         if (typeof(func) == "function") {
562             return function (obj) {
563                 return func.apply(obj, args);
564             };
565         } else {
566             return function (obj) {
567                 return obj[func].apply(obj, args);
568             };
569         }
570     },
572     /** @id MochiKit.Base.method */
573     method: function (self, func) {
574         var m = MochiKit.Base;
575         return m.bind.apply(this, m.extend([func, self], arguments, 2));
576     },
578     /** @id MochiKit.Base.compose */
579     compose: function (f1, f2/*, f3, ... fN */) {
580         var fnlist = [];
581         var m = MochiKit.Base;
582         if (arguments.length === 0) {
583             throw new TypeError("compose() requires at least one argument");
584         }
585         for (var i = 0; i < arguments.length; i++) {
586             var fn = arguments[i];
587             if (typeof(fn) != "function") {
588                 throw new TypeError(m.repr(fn) + " is not a function");
589             }
590             fnlist.push(fn);
591         }
592         return function () {
593             var args = arguments;
594             for (var i = fnlist.length - 1; i >= 0; i--) {
595                 args = [fnlist[i].apply(this, args)];
596             }
597             return args[0];
598         };
599     },
601     /** @id MochiKit.Base.bind */
602     bind: function (func, self/* args... */) {
603         if (typeof(func) == "string") {
604             func = self[func];
605         }
606         var im_func = func.im_func;
607         var im_preargs = func.im_preargs;
608         var im_self = func.im_self;
609         var m = MochiKit.Base;
610         if (typeof(func) == "function" && typeof(func.apply) == "undefined") {
611             // this is for cases where JavaScript sucks ass and gives you a
612             // really dumb built-in function like alert() that doesn't have
613             // an apply
614             func = m._wrapDumbFunction(func);
615         }
616         if (typeof(im_func) != 'function') {
617             im_func = func;
618         }
619         if (typeof(self) != 'undefined') {
620             im_self = self;
621         }
622         if (typeof(im_preargs) == 'undefined') {
623             im_preargs = [];
624         } else  {
625             im_preargs = im_preargs.slice();
626         }
627         m.extend(im_preargs, arguments, 2);
628         var newfunc = function () {
629             var args = arguments;
630             var me = arguments.callee;
631             if (me.im_preargs.length > 0) {
632                 args = m.concat(me.im_preargs, args);
633             }
634             var self = me.im_self;
635             if (!self) {
636                 self = this;
637             }
638             return me.im_func.apply(self, args);
639         };
640         newfunc.im_self = im_self;
641         newfunc.im_func = im_func;
642         newfunc.im_preargs = im_preargs;
643         return newfunc;
644     },
646     /** @id MochiKit.Base.bindMethods */
647     bindMethods: function (self) {
648         var bind = MochiKit.Base.bind;
649         for (var k in self) {
650             var func = self[k];
651             if (typeof(func) == 'function') {
652                 self[k] = bind(func, self);
653             }
654         }
655     },
657     /** @id MochiKit.Base.registerComparator */
658     registerComparator: function (name, check, comparator, /* optional */ override) {
659         MochiKit.Base.comparatorRegistry.register(name, check, comparator, override);
660     },
662     _primitives: {'boolean': true, 'string': true, 'number': true},
664     /** @id MochiKit.Base.compare */
665     compare: function (a, b) {
666         if (a == b) {
667             return 0;
668         }
669         var aIsNull = (typeof(a) == 'undefined' || a === null);
670         var bIsNull = (typeof(b) == 'undefined' || b === null);
671         if (aIsNull && bIsNull) {
672             return 0;
673         } else if (aIsNull) {
674             return -1;
675         } else if (bIsNull) {
676             return 1;
677         }
678         var m = MochiKit.Base;
679         // bool, number, string have meaningful comparisons
680         var prim = m._primitives;
681         if (!(typeof(a) in prim && typeof(b) in prim)) {
682             try {
683                 return m.comparatorRegistry.match(a, b);
684             } catch (e) {
685                 if (e != m.NotFound) {
686                     throw e;
687                 }
688             }
689         }
690         if (a < b) {
691             return -1;
692         } else if (a > b) {
693             return 1;
694         }
695         // These types can't be compared
696         var repr = m.repr;
697         throw new TypeError(repr(a) + " and " + repr(b) + " can not be compared");
698     },
700     /** @id MochiKit.Base.compareDateLike */
701     compareDateLike: function (a, b) {
702         return MochiKit.Base.compare(a.getTime(), b.getTime());
703     },
705     /** @id MochiKit.Base.compareArrayLike */
706     compareArrayLike: function (a, b) {
707         var compare = MochiKit.Base.compare;
708         var count = a.length;
709         var rval = 0;
710         if (count > b.length) {
711             rval = 1;
712             count = b.length;
713         } else if (count < b.length) {
714             rval = -1;
715         }
716         for (var i = 0; i < count; i++) {
717             var cmp = compare(a[i], b[i]);
718             if (cmp) {
719                 return cmp;
720             }
721         }
722         return rval;
723     },
725     /** @id MochiKit.Base.registerRepr */
726     registerRepr: function (name, check, wrap, /* optional */override) {
727         MochiKit.Base.reprRegistry.register(name, check, wrap, override);
728     },
730     /** @id MochiKit.Base.repr */
731     repr: function (o) {
732         if (typeof(o) == "undefined") {
733             return "undefined";
734         } else if (o === null) {
735             return "null";
736         }
737         try {
738             if (typeof(o.__repr__) == 'function') {
739                 return o.__repr__();
740             } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
741                 return o.repr();
742             }
743             return MochiKit.Base.reprRegistry.match(o);
744         } catch (e) {
745             if (typeof(o.NAME) == 'string' && (
746                     o.toString == Function.prototype.toString ||
747                     o.toString == Object.prototype.toString
748                 )) {
749                 return o.NAME;
750             }
751         }
752         try {
753             var ostring = (o + "");
754         } catch (e) {
755             return "[" + typeof(o) + "]";
756         }
757         if (typeof(o) == "function") {
758             o = ostring.replace(/^\s+/, "");
759             var idx = o.indexOf("{");
760             if (idx != -1) {
761                 o = o.substr(0, idx) + "{...}";
762             }
763         }
764         return ostring;
765     },
767     /** @id MochiKit.Base.reprArrayLike */
768     reprArrayLike: function (o) {
769         var m = MochiKit.Base;
770         return "[" + m.map(m.repr, o).join(", ") + "]";
771     },
773     /** @id MochiKit.Base.reprString */
774     reprString: function (o) {
775         return ('"' + o.replace(/(["\\])/g, '\\$1') + '"'
776             ).replace(/[\f]/g, "\\f"
777             ).replace(/[\b]/g, "\\b"
778             ).replace(/[\n]/g, "\\n"
779             ).replace(/[\t]/g, "\\t"
780             ).replace(/[\r]/g, "\\r");
781     },
783     /** @id MochiKit.Base.reprNumber */
784     reprNumber: function (o) {
785         return o + "";
786     },
788     /** @id MochiKit.Base.registerJSON */
789     registerJSON: function (name, check, wrap, /* optional */override) {
790         MochiKit.Base.jsonRegistry.register(name, check, wrap, override);
791     },
794     /** @id MochiKit.Base.evalJSON */
795     evalJSON: function () {
796         return eval("(" + arguments[0] + ")");
797     },
799     /** @id MochiKit.Base.serializeJSON */
800     serializeJSON: function (o) {
801         var objtype = typeof(o);
802         if (objtype == "number" || objtype == "boolean") {
803             return o + "";
804         } else if (o === null) {
805             return "null";
806         }
807         var m = MochiKit.Base;
808         var reprString = m.reprString;
809         if (objtype == "string") {
810             return reprString(o);
811         }
812         // recurse
813         var me = arguments.callee;
814         // short-circuit for objects that support "json" serialization
815         // if they return "self" then just pass-through...
816         var newObj;
817         if (typeof(o.__json__) == "function") {
818             newObj = o.__json__();
819             if (o !== newObj) {
820                 return me(newObj);
821             }
822         }
823         if (typeof(o.json) == "function") {
824             newObj = o.json();
825             if (o !== newObj) {
826                 return me(newObj);
827             }
828         }
829         // array
830         if (objtype != "function" && typeof(o.length) == "number") {
831             var res = [];
832             for (var i = 0; i < o.length; i++) {
833                 var val = me(o[i]);
834                 if (typeof(val) != "string") {
835                     val = "undefined";
836                 }
837                 res.push(val);
838             }
839             return "[" + res.join(", ") + "]";
840         }
841         // look in the registry
842         try {
843             newObj = m.jsonRegistry.match(o);
844             if (o !== newObj) {
845                 return me(newObj);
846             }
847         } catch (e) {
848             if (e != m.NotFound) {
849                 // something really bad happened
850                 throw e;
851             }
852         }
853         // undefined is outside of the spec
854         if (objtype == "undefined") {
855             throw new TypeError("undefined can not be serialized as JSON");
856         }
857         // it's a function with no adapter, bad
858         if (objtype == "function") {
859             return null;
860         }
861         // generic object code path
862         res = [];
863         for (var k in o) {
864             var useKey;
865             if (typeof(k) == "number") {
866                 useKey = '"' + k + '"';
867             } else if (typeof(k) == "string") {
868                 useKey = reprString(k);
869             } else {
870                 // skip non-string or number keys
871                 continue;
872             }
873             val = me(o[k]);
874             if (typeof(val) != "string") {
875                 // skip non-serializable values
876                 continue;
877             }
878             res.push(useKey + ":" + val);
879         }
880         return "{" + res.join(", ") + "}";
881     },
884     /** @id MochiKit.Base.objEqual */
885     objEqual: function (a, b) {
886         return (MochiKit.Base.compare(a, b) === 0);
887     },
889     /** @id MochiKit.Base.arrayEqual */
890     arrayEqual: function (self, arr) {
891         if (self.length != arr.length) {
892             return false;
893         }
894         return (MochiKit.Base.compare(self, arr) === 0);
895     },
897     /** @id MochiKit.Base.concat */
898     concat: function (/* lst... */) {
899         var rval = [];
900         var extend = MochiKit.Base.extend;
901         for (var i = 0; i < arguments.length; i++) {
902             extend(rval, arguments[i]);
903         }
904         return rval;
905     },
907     /** @id MochiKit.Base.keyComparator */
908     keyComparator: function (key/* ... */) {
909         // fast-path for single key comparisons
910         var m = MochiKit.Base;
911         var compare = m.compare;
912         if (arguments.length == 1) {
913             return function (a, b) {
914                 return compare(a[key], b[key]);
915             };
916         }
917         var compareKeys = m.extend(null, arguments);
918         return function (a, b) {
919             var rval = 0;
920             // keep comparing until something is inequal or we run out of
921             // keys to compare
922             for (var i = 0; (rval === 0) && (i < compareKeys.length); i++) {
923                 var key = compareKeys[i];
924                 rval = compare(a[key], b[key]);
925             }
926             return rval;
927         };
928     },
930     /** @id MochiKit.Base.reverseKeyComparator */
931     reverseKeyComparator: function (key) {
932         var comparator = MochiKit.Base.keyComparator.apply(this, arguments);
933         return function (a, b) {
934             return comparator(b, a);
935         };
936     },
938     /** @id MochiKit.Base.partial */
939     partial: function (func) {
940         var m = MochiKit.Base;
941         return m.bind.apply(this, m.extend([func, undefined], arguments, 1));
942     },
944     /** @id MochiKit.Base.listMinMax */
945     listMinMax: function (which, lst) {
946         if (lst.length === 0) {
947             return null;
948         }
949         var cur = lst[0];
950         var compare = MochiKit.Base.compare;
951         for (var i = 1; i < lst.length; i++) {
952             var o = lst[i];
953             if (compare(o, cur) == which) {
954                 cur = o;
955             }
956         }
957         return cur;
958     },
960     /** @id MochiKit.Base.objMax */
961     objMax: function (/* obj... */) {
962         return MochiKit.Base.listMinMax(1, arguments);
963     },
965     /** @id MochiKit.Base.objMin */
966     objMin: function (/* obj... */) {
967         return MochiKit.Base.listMinMax(-1, arguments);
968     },
970     /** @id MochiKit.Base.findIdentical */
971     findIdentical: function (lst, value, start/* = 0 */, /* optional */end) {
972         if (typeof(end) == "undefined" || end === null) {
973             end = lst.length;
974         }
975         if (typeof(start) == "undefined" || start === null) {
976             start = 0;
977         }
978         for (var i = start; i < end; i++) {
979             if (lst[i] === value) {
980                 return i;
981             }
982         }
983         return -1;
984     },
986     /** @id MochiKit.Base.mean */
987     mean: function(/* lst... */) {
988         /* http://www.nist.gov/dads/HTML/mean.html */
989         var sum = 0;
991         var m = MochiKit.Base;
992         var args = m.extend(null, arguments);
993         var count = args.length;
995         while (args.length) {
996             var o = args.shift();
997             if (o && typeof(o) == "object" && typeof(o.length) == "number") {
998                 count += o.length - 1;
999                 for (var i = o.length - 1; i >= 0; i--) {
1000                     sum += o[i];
1001                 }
1002             } else {
1003                 sum += o;
1004             }
1005         }
1007         if (count <= 0) {
1008             throw new TypeError('mean() requires at least one argument');
1009         }
1011         return sum/count;
1012     },
1014     /** @id MochiKit.Base.median */
1015     median: function(/* lst... */) {
1016         /* http://www.nist.gov/dads/HTML/median.html */
1017         var data = MochiKit.Base.flattenArguments(arguments);
1018         if (data.length === 0) {
1019             throw new TypeError('median() requires at least one argument');
1020         }
1021         data.sort(compare);
1022         if (data.length % 2 == 0) {
1023             var upper = data.length / 2;
1024             return (data[upper] + data[upper - 1]) / 2;
1025         } else {
1026             return data[(data.length - 1) / 2];
1027         }
1028     },
1030     /** @id MochiKit.Base.findValue */
1031     findValue: function (lst, value, start/* = 0 */, /* optional */end) {
1032         if (typeof(end) == "undefined" || end === null) {
1033             end = lst.length;
1034         }
1035         if (typeof(start) == "undefined" || start === null) {
1036             start = 0;
1037         }
1038         var cmp = MochiKit.Base.compare;
1039         for (var i = start; i < end; i++) {
1040             if (cmp(lst[i], value) === 0) {
1041                 return i;
1042             }
1043         }
1044         return -1;
1045     },
1047     /** @id MochiKit.Base.nodeWalk */
1048     nodeWalk: function (node, visitor) {
1049         var nodes = [node];
1050         var extend = MochiKit.Base.extend;
1051         while (nodes.length) {
1052             var res = visitor(nodes.shift());
1053             if (res) {
1054                 extend(nodes, res);
1055             }
1056         }
1057     },
1060     /** @id MochiKit.Base.nameFunctions */
1061     nameFunctions: function (namespace) {
1062         var base = namespace.NAME;
1063         if (typeof(base) == 'undefined') {
1064             base = '';
1065         } else {
1066             base = base + '.';
1067         }
1068         for (var name in namespace) {
1069             var o = namespace[name];
1070             if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
1071                 try {
1072                     o.NAME = base + name;
1073                 } catch (e) {
1074                     // pass
1075                 }
1076             }
1077         }
1078     },
1081     /** @id MochiKit.Base.queryString */
1082     queryString: function (names, values) {
1083         // check to see if names is a string or a DOM element, and if
1084         // MochiKit.DOM is available.  If so, drop it like it's a form
1085         // Ugliest conditional in MochiKit?  Probably!
1086         if (typeof(MochiKit.DOM) != "undefined" && arguments.length == 1
1087             && (typeof(names) == "string" || (
1088                 typeof(names.nodeType) != "undefined" && names.nodeType > 0
1089             ))
1090         ) {
1091             var kv = MochiKit.DOM.formContents(names);
1092             names = kv[0];
1093             values = kv[1];
1094         } else if (arguments.length == 1) {
1095             // Allow the return value of formContents to be passed directly
1096             if (typeof(names.length) == "number" && names.length == 2) {
1097                 return arguments.callee(names[0], names[1]);
1098             }
1099             var o = names;
1100             names = [];
1101             values = [];
1102             for (var k in o) {
1103                 var v = o[k];
1104                 if (typeof(v) == "function") {
1105                     continue;
1106                 } else if (typeof(v) != "string" &&
1107                         typeof(v.length) == "number") {
1108                     for (var i = 0; i < v.length; i++) {
1109                         names.push(k);
1110                         values.push(v[i]);
1111                     }
1112                 } else {
1113                     names.push(k);
1114                     values.push(v);
1115                 }
1116             }
1117         }
1118         var rval = [];
1119         var len = Math.min(names.length, values.length);
1120         var urlEncode = MochiKit.Base.urlEncode;
1121         for (var i = 0; i < len; i++) {
1122             v = values[i];
1123             if (typeof(v) != 'undefined' && v !== null) {
1124                 rval.push(urlEncode(names[i]) + "=" + urlEncode(v));
1125             }
1126         }
1127         return rval.join("&");
1128     },
1131     /** @id MochiKit.Base.parseQueryString */
1132     parseQueryString: function (encodedString, useArrays) {
1133         // strip a leading '?' from the encoded string
1134         var qstr = (encodedString.charAt(0) == "?")
1135             ? encodedString.substring(1)
1136             : encodedString;
1137         var pairs = qstr.replace(/\+/g, "%20").split(/(\&amp\;|\&\#38\;|\&#x26;|\&)/);
1138         var o = {};
1139         var decode;
1140         if (typeof(decodeURIComponent) != "undefined") {
1141             decode = decodeURIComponent;
1142         } else {
1143             decode = unescape;
1144         }
1145         if (useArrays) {
1146             for (var i = 0; i < pairs.length; i++) {
1147                 var pair = pairs[i].split("=");
1148                 if (pair.length !== 2) {
1149                     continue;
1150                 }
1151                 var name = decode(pair[0]);
1152                 var arr = o[name];
1153                 if (!(arr instanceof Array)) {
1154                     arr = [];
1155                     o[name] = arr;
1156                 }
1157                 arr.push(decode(pair[1]));
1158             }
1159         } else {
1160             for (i = 0; i < pairs.length; i++) {
1161                 pair = pairs[i].split("=");
1162                 if (pair.length !== 2) {
1163                     continue;
1164                 }
1165                 o[decode(pair[0])] = decode(pair[1]);
1166             }
1167         }
1168         return o;
1169     }
1172 /** @id MochiKit.Base.AdapterRegistry */
1173 MochiKit.Base.AdapterRegistry = function () {
1174     this.pairs = [];
1177 MochiKit.Base.AdapterRegistry.prototype = {
1178     /** @id MochiKit.Base.AdapterRegistry.prototype.register */
1179     register: function (name, check, wrap, /* optional */ override) {
1180         if (override) {
1181             this.pairs.unshift([name, check, wrap]);
1182         } else {
1183             this.pairs.push([name, check, wrap]);
1184         }
1185     },
1187     /** @id MochiKit.Base.AdapterRegistry.prototype.match */
1188     match: function (/* ... */) {
1189         for (var i = 0; i < this.pairs.length; i++) {
1190             var pair = this.pairs[i];
1191             if (pair[1].apply(this, arguments)) {
1192                 return pair[2].apply(this, arguments);
1193             }
1194         }
1195         throw MochiKit.Base.NotFound;
1196     },
1198     /** @id MochiKit.Base.AdapterRegistry.prototype.unregister */
1199     unregister: function (name) {
1200         for (var i = 0; i < this.pairs.length; i++) {
1201             var pair = this.pairs[i];
1202             if (pair[0] == name) {
1203                 this.pairs.splice(i, 1);
1204                 return true;
1205             }
1206         }
1207         return false;
1208     }
1212 MochiKit.Base.EXPORT = [
1213     "flattenArray",
1214     "noop",
1215     "camelize",
1216     "counter",
1217     "clone",
1218     "extend",
1219     "update",
1220     "updatetree",
1221     "setdefault",
1222     "keys",
1223     "values",
1224     "items",
1225     "NamedError",
1226     "operator",
1227     "forwardCall",
1228     "itemgetter",
1229     "typeMatcher",
1230     "isCallable",
1231     "isUndefined",
1232     "isUndefinedOrNull",
1233     "isNull",
1234     "isEmpty",
1235     "isNotEmpty",
1236     "isArrayLike",
1237     "isDateLike",
1238     "xmap",
1239     "map",
1240     "xfilter",
1241     "filter",
1242     "methodcaller",
1243     "compose",
1244     "bind",
1245     "bindMethods",
1246     "NotFound",
1247     "AdapterRegistry",
1248     "registerComparator",
1249     "compare",
1250     "registerRepr",
1251     "repr",
1252     "objEqual",
1253     "arrayEqual",
1254     "concat",
1255     "keyComparator",
1256     "reverseKeyComparator",
1257     "partial",
1258     "merge",
1259     "listMinMax",
1260     "listMax",
1261     "listMin",
1262     "objMax",
1263     "objMin",
1264     "nodeWalk",
1265     "zip",
1266     "urlEncode",
1267     "queryString",
1268     "serializeJSON",
1269     "registerJSON",
1270     "evalJSON",
1271     "parseQueryString",
1272     "findValue",
1273     "findIdentical",
1274     "flattenArguments",
1275     "method",
1276     "average",
1277     "mean",
1278     "median"
1281 MochiKit.Base.EXPORT_OK = [
1282     "nameFunctions",
1283     "comparatorRegistry",
1284     "reprRegistry",
1285     "jsonRegistry",
1286     "compareDateLike",
1287     "compareArrayLike",
1288     "reprArrayLike",
1289     "reprString",
1290     "reprNumber"
1293 MochiKit.Base._exportSymbols = function (globals, module) {
1294     if (!MochiKit.__export__) {
1295         return;
1296     }
1297     var all = module.EXPORT_TAGS[":all"];
1298     for (var i = 0; i < all.length; i++) {
1299         globals[all[i]] = module[all[i]];
1300     }
1303 MochiKit.Base.__new__ = function () {
1304     // A singleton raised when no suitable adapter is found
1305     var m = this;
1307     // convenience
1308     /** @id MochiKit.Base.noop */
1309     m.noop = m.operator.identity;
1311     // Backwards compat
1312     m.forward = m.forwardCall;
1313     m.find = m.findValue;
1315     if (typeof(encodeURIComponent) != "undefined") {
1316         /** @id MochiKit.Base.urlEncode */
1317         m.urlEncode = function (unencoded) {
1318             return encodeURIComponent(unencoded).replace(/\'/g, '%27');
1319         };
1320     } else {
1321         m.urlEncode = function (unencoded) {
1322             return escape(unencoded
1323                 ).replace(/\+/g, '%2B'
1324                 ).replace(/\"/g,'%22'
1325                 ).rval.replace(/\'/g, '%27');
1326         };
1327     }
1329     /** @id MochiKit.Base.NamedError */
1330     m.NamedError = function (name) {
1331         this.message = name;
1332         this.name = name;
1333     };
1334     m.NamedError.prototype = new Error();
1335     m.update(m.NamedError.prototype, {
1336         repr: function () {
1337             if (this.message && this.message != this.name) {
1338                 return this.name + "(" + m.repr(this.message) + ")";
1339             } else {
1340                 return this.name + "()";
1341             }
1342         },
1343         toString: m.forwardCall("repr")
1344     });
1346     /** @id MochiKit.Base.NotFound */
1347     m.NotFound = new m.NamedError("MochiKit.Base.NotFound");
1350     /** @id MochiKit.Base.listMax */
1351     m.listMax = m.partial(m.listMinMax, 1);
1352     /** @id MochiKit.Base.listMin */
1353     m.listMin = m.partial(m.listMinMax, -1);
1355     /** @id MochiKit.Base.isCallable */
1356     m.isCallable = m.typeMatcher('function');
1357     /** @id MochiKit.Base.isUndefined */
1358     m.isUndefined = m.typeMatcher('undefined');
1360     /** @id MochiKit.Base.merge */
1361     m.merge = m.partial(m.update, null);
1362     /** @id MochiKit.Base.zip */
1363     m.zip = m.partial(m.map, null);
1365     /** @id MochiKit.Base.average */
1366     m.average = m.mean;
1368     /** @id MochiKit.Base.comparatorRegistry */
1369     m.comparatorRegistry = new m.AdapterRegistry();
1370     m.registerComparator("dateLike", m.isDateLike, m.compareDateLike);
1371     m.registerComparator("arrayLike", m.isArrayLike, m.compareArrayLike);
1373     /** @id MochiKit.Base.reprRegistry */
1374     m.reprRegistry = new m.AdapterRegistry();
1375     m.registerRepr("arrayLike", m.isArrayLike, m.reprArrayLike);
1376     m.registerRepr("string", m.typeMatcher("string"), m.reprString);
1377     m.registerRepr("numbers", m.typeMatcher("number", "boolean"), m.reprNumber);
1379     /** @id MochiKit.Base.jsonRegistry */
1380     m.jsonRegistry = new m.AdapterRegistry();
1382     var all = m.concat(m.EXPORT, m.EXPORT_OK);
1383     m.EXPORT_TAGS = {
1384         ":common": m.concat(m.EXPORT_OK),
1385         ":all": all
1386     };
1388     m.nameFunctions(this);
1392 MochiKit.Base.__new__();
1395 // XXX: Internet Explorer blows
1397 if (MochiKit.__export__) {
1398     compare = MochiKit.Base.compare;
1399     compose = MochiKit.Base.compose;
1400     serializeJSON = MochiKit.Base.serializeJSON;
1403 MochiKit.Base._exportSymbols(this, MochiKit.Base);