/* appjet:version 0.1 */
/* appjet:library */
// Copyright (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)
/**@overview This library contains various utility functions
* that appeared during developing other apps but are general enough.
*/
/**
* Returns global object.
*/
function global() { return {}.valueOf.call(); }
/**
* Read-only wrapper on an object.
*/
function readonly(o) {
return appjet._native.createProxyObject(
function(name) { return o[name]; },
function() {},
function() {},
function() { return []; },
o
);
}
/**
* Extends an object by copying all other object's own properties.
* Returns the extended object. This implies you can create clone
* of simple object by extend({}, x).
*/
function extend(dst, src) {
eachProperty(src, function(p) { dst[p] = src[p]; });
return dst;
}
/**
* Akin to Array >> streamContents:
in Smalltalk.
* Calls f and returns array of every item that was printed using print.
* Useful for collecting any item in some loop(s) for which
* filter or map can not be straightforwardly used. Both examples get keys
* of own properties
@example
collectPrint(function() { eachProperty(object, print); });
collectPrint(function() {for (var p in object) { if (object.hasOwnProperty(p) print(p); }});
*/
function collectPrint(f) {
var result = [];
var _g = global();
var savedPrint = _g.print;
_g.print = function() { result.push.apply(result, arguments); }; // cannot inline, or f***ing try/finally bug which nobody is willing to fix appears
try { f(); } finally { _g.print = savedPrint; }
return result;
}
function _collector(result) { return function() { result.push.apply(result, arguments); }; }
/**
* Akin to Array >> streamContents:
in Smalltalk.
* Calls f and returns array of every item that was yielded using yield.
* Useful for collecting any item in some loop(s) for which
* filter or map can not be straightforwardly used. An example gets keys
* of own properties
@example
collectYield(function () {for (var p in object) { if (object.hasOwnProperty(p) yield p; }});
*/
/*function collectYield(f) {
var result = [];
var gen = f();
try {
for(;;) { result.push(gen.next()); }
} catch (ex) {
if (ex instanceof StopIteration) {
//gen.close(); //strange, not needed
} else throw ex;
}
return result;
}*/
//from web; then edited
/** Converts h,s,v (0-1) to rgb (0-255). Returns array [r, g, b]. */
function hsv2rgb(h,s,v) {
// Adapted from http://www.easyrgb.com/math.html
// hsv values = 0 - 1, rgb values = 0 - 255
var r, g, b;
if (s == 0) {
r = g = b = v;
} else {
var vh = 6*h;
var vi = Math.floor(vh);
var v1 = v*(1-s);
var v2 = v*(1-s*(vh-vi));
var v3 = v*(1-s*(1-(vh-vi)));
switch(vi) {
case 0: r = v; g = v3; b = v1; break;
case 1: r = v2; g = v; b = v1; break;
case 2: r = v1; g = v; b = v3; break;
case 3: r = v1; g = v2; b = v; break;
case 4: r = v3; g = v1; b = v; break;
case 5: r = v; g = v1; b = v2; break;
}
}
return [ Math.round(255*r), Math.round(255*g), Math.round(255*b) ];
}
/**
* Call this function with etag of actual requested resource.
* Includes caching headers in the response,
* and if the ETag matches, it immediately stops the response
* with "304 Not Modified"; in other case it returns
* and lets you continue with populating the response data.
* Call after you KNOW the resource exists! It is illegal
* to change response status to 404 or similar after this call.
*/
function etagCaching(etag) {
response.setCacheable(null);
response.setHeader("Cache-Control", "public, must-revalidate");
response.setHeader("ETag", etag);
var cond = request.headers["If-None-Match"];
if (cond && (cond === "*" || ([].concat(cond).indexOf(etag) !== -1))) {
response.setStatusCode(304);
response.stop();
}
}
/**
* Splits all elements of an array by delimiter
* and insert o in places where split occurred.
* Returns modified array with split parts and delimiting o's.
*/
function splitReplace(array, delimiter, o) {
var i, j, k, l = 0, m = array.length;
array.forEach(function (v, k, a) {
var split = a[k] = v.split ? v.split(delimiter) : [v];
l += 2 * split.length - 1;
});
array.length = l;
for (var i = m-1, j = l-1; i >= 0; i--) {
var parts = array[i];
for (var k = parts.length - 1; k > 0; --k) {
array[j--] = parts[k--];
array[j--] = o;
}
array[j--] = parts[0];
}
return array;
}
/* appjet:server */
import("lib-support/jsunit");
var _l = import({}, "lib-support/useful");
// ==== Test cases ====
page.testSuite = [
function collectPrintCollectsPrintedArguments() {
var collected = _l.collectPrint(function() {
print("Hello", "world", "!");
print();
print(3,1,4);
});
assertArrayEquals("Incorrect items collected.", ["Hello", "world", "!", 3, 1, 4], collected);
},
function collectPrintRetainsLegacyPrint() {
var savedPrint = this.print;
_l.collectPrint(function() {
print("Hello", "world", "!");
print();
print(3,1,4);
});
assertEquals("Print not retained.", savedPrint, this.print);
},
function collectPrintRetainsLegacyPrintInCaseOfException() {
var savedPrint = this.print;
var f = function() { throw "exception"; };
try {
_l.collectPrint(f);
fail("Exception not thrown");
} catch(ex) {
if (ex === "exception") {
// pass
} else throw ex;
}
assertEquals("Print not retained.", savedPrint, this.print);
},
function collectPrintRetainsLegacyPrintInCaseOfUndefinedException() {
var savedPrint = this.print;
var f = function() { throw undefined; };
try {
_l.collectPrint(f);
fail("Exception not thrown");
} catch(ex) {
if (ex === undefined) {
// pass
} else throw ex;
}
assertEquals("Print not retained.", savedPrint, this.print);
},
/*function collectYieldCollectsYieldedArguments() {
var collected = _l.collectYield(function() {
yield "Hello";
yield 3;
});
assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
},
function collectYieldCollectsYieldeesFromTryBlockAndFinallyRuns() {
var frun = false;
var collected = _l.collectYield(function() {
try {
yield "Hello";
yield 3;
} finally { frun = true; }
});
assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
assertTrue("Finally did not run", frun);
},
function collectYieldThrowsExceptionIfThrownInF() {
try {
var collected = _l.collectYield(function() {
yield "Hello";
throw "exception";
yield "world";
});
fail("Exception not thrown.");
} catch(ex) {
if (ex === "exception") {
// pass
} else throw ex;
}
},
function collectYieldThrowsUndefinedIfThrownInF() {
try {
var collected = _l.collectYield(function() {
yield "Hello";
throw undefined;
yield "world";
});
fail("Exception not thrown.");
} catch(ex) {
if (ex === undefined) {
// pass
} else throw ex;
}
},
function collectYieldThrowsExceptionIfExceptionThrownInF_AndFinallyInFRuns() {
var finallyRun = false;
try {
var collected = _l.collectYield(function() {
try {
yield "Hello";
throw "exception";
yield "world";
} finally { finallyRun = true; }
});
fail("Exception not thrown.");
} catch(ex) {
if (ex === "exception") {
// pass
} else throw ex;
}
assertTrue("Finally in f did not run.", finallyRun);
},*/
function readonlyReadsCorrectlyAndIsDynamic() {
var x = {hello: "world"};
var rx = _l.readonly(x);
assertEquals("Hello read incorrectly", "world", rx.hello);
assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
x.hello = "again";
x.thisIsNot = "this is";
assertEquals("Hello read incorrectly", "again", rx.hello);
assertEquals("ThisIsNot read incorrectly", "this is", rx.thisIsNot);
},
function readonlyIsReadOnly() {
var x = {hello: "world"};
var rx = _l.readonly(x);
rx.thisIsNot = "this is";
delete rx.hello;
assertEquals("Hello read incorrectly", "world", rx.hello);
assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
},
function extendExtendsCorrectlyForAllCombinations() {
var srcProto = { pro: "to" };
var src = object(srcProto);
src.a = "b"; src.c = "d";
var dst = { pro: "fi", a: "bc", yes: "true" };
var result = _l.extend(dst, src);
assertEquals("extend did not return extended object", dst, result);
assertEquals("a wasn't copied", "b", dst.a);
assertEquals("c wasn't copied", "d", dst.c);
assertEquals("pro was changed", "fi", dst.pro);
assertEquals("yes was changed", "true", dst.yes);
},
//TODO tests for splitReplace
];
// ==== Test runner ====
import("lib-support/runner");
runTestsByDefault();