useful.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /* appjet:version 0.1 */
  2. /* appjet:library */
  3. // Copyright (c) 2009, Herbert Vojčík
  4. // Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)
  5. /**@overview This library contains various utility functions
  6. * that appeared during developing other apps but are general enough.
  7. */
  8. /**
  9. * Returns global object.
  10. */
  11. function global() { return {}.valueOf.call(); }
  12. /**
  13. * Returns stub function that logs arguments called
  14. * into its property called log and returns retval.
  15. * Useful primarily for testing.
  16. */
  17. function stub(retval) {
  18. var result = function() {
  19. result.log.push(Array.slice(arguments));
  20. return retval;
  21. };
  22. result.log = [];
  23. return result;
  24. }
  25. /**
  26. * Read-only wrapper on an object.
  27. */
  28. function readonly(o) {
  29. return appjet._native.createProxyObject(
  30. function(name) { return o[name]; },
  31. function() {},
  32. function() {},
  33. function() { return []; },
  34. o
  35. );
  36. }
  37. /**
  38. * Extends an object by copying all other object's own properties.
  39. * Returns the extended object. This implies you can create clone
  40. * of simple object by extend({}, x).
  41. */
  42. function extend(dst, src) {
  43. eachProperty(src, function(p) { dst[p] = src[p]; });
  44. return dst;
  45. }
  46. /**
  47. * Akin to <code>Array >> streamContents:</code> in Smalltalk.
  48. * Calls f and returns array of every item that was printed using print.
  49. * Useful for collecting any item in some loop(s) for which
  50. * filter or map can not be straightforwardly used. Both examples get keys
  51. * of own properties
  52. @example
  53. collectPrint(function() { eachProperty(object, print); });
  54. collectPrint(function() {for (var p in object) { if (object.hasOwnProperty(p) print(p); }});
  55. */
  56. function collectPrint(f) {
  57. var result = [];
  58. var _g = global();
  59. var savedPrint = _g.print;
  60. _g.print = function() { result.push.apply(result, arguments); }; // cannot inline, or f***ing try/finally bug which nobody is willing to fix appears
  61. try { f(); } finally { _g.print = savedPrint; }
  62. return result;
  63. }
  64. function _collector(result) { return function() { result.push.apply(result, arguments); }; }
  65. /**
  66. * Akin to <code>Array >> streamContents:</code> in Smalltalk.
  67. * Calls f and returns array of every item that was yielded using yield.
  68. * Useful for collecting any item in some loop(s) for which
  69. * filter or map can not be straightforwardly used. An example gets keys
  70. * of own properties
  71. @example
  72. collectYield(function () {for (var p in object) { if (object.hasOwnProperty(p) yield p; }});
  73. */
  74. function collectYield(f) {
  75. var result = [];
  76. var gen = f();
  77. try {
  78. for(;;) { result.push(gen.next()); }
  79. } catch (ex if ex instanceof StopIteration) {
  80. //gen.close(); //strange, not needed
  81. }
  82. return result;
  83. }
  84. //from web; then edited
  85. /** Converts h,s,v (0-1) to rgb (0-255). Returns array [r, g, b]. */
  86. function hsv2rgb(h,s,v) {
  87. // Adapted from http://www.easyrgb.com/math.html
  88. // hsv values = 0 - 1, rgb values = 0 - 255
  89. var r, g, b;
  90. if (s == 0) {
  91. r = g = b = v;
  92. } else {
  93. var vh = 6*h;
  94. var vi = Math.floor(vh);
  95. var v1 = v*(1-s);
  96. var v2 = v*(1-s*(vh-vi));
  97. var v3 = v*(1-s*(1-(vh-vi)));
  98. switch(vi) {
  99. case 0: r = v; g = v3; b = v1; break;
  100. case 1: r = v2; g = v; b = v1; break;
  101. case 2: r = v1; g = v; b = v3; break;
  102. case 3: r = v1; g = v2; b = v; break;
  103. case 4: r = v3; g = v1; b = v; break;
  104. case 5: r = v; g = v1; b = v2; break;
  105. }
  106. }
  107. return [ Math.round(255*r), Math.round(255*g), Math.round(255*b) ];
  108. }
  109. /**
  110. * Call this function with etag of actual requested resource.
  111. * Includes caching headers in the response,
  112. * and if the ETag matches, it immediately stops the response
  113. * with "304 Not Modified"; in other case it returns
  114. * and lets you continue with populating the response data.
  115. * Call after you KNOW the resource exists! It is illegal
  116. * to change response status to 404 or similar after this call.
  117. */
  118. function etagCaching(etag) {
  119. response.setCacheable(null);
  120. response.setHeader("Cache-Control", "public, must-revalidate");
  121. response.setHeader("ETag", etag);
  122. var cond = request.headers["If-None-Match"];
  123. if (cond && (cond === "*" || ([].concat(cond).indexOf(etag) !== -1))) {
  124. response.setStatusCode(304);
  125. response.stop();
  126. }
  127. }
  128. /**
  129. * Splits all elements of an array by delimiter
  130. * and insert o in places where split occurred.
  131. * Returns modified array with split parts and delimiting o's.
  132. */
  133. function splitReplace(array, delimiter, o) {
  134. var i, j, k, l = 0, m = array.length;
  135. array.forEach(function (v, k, a) {
  136. var split = a[k] = v.split ? v.split(delimiter) : [v];
  137. l += 2 * split.length - 1;
  138. });
  139. array.length = l;
  140. for (var i = m-1, j = l-1; i >= 0; i--) {
  141. var parts = array[i];
  142. for (var k = parts.length - 1; k > 0; --k) {
  143. array[j--] = parts[k--];
  144. array[j--] = o;
  145. }
  146. array[j--] = parts[0];
  147. }
  148. return array;
  149. }
  150. /* appjet:server */
  151. import("lib-support/jsunit");
  152. var _l = import({}, "lib-support/useful");
  153. // ==== Test cases ====
  154. page.testSuite = [
  155. function collectPrintCollectsPrintedArguments() {
  156. var collected = _l.collectPrint(function() {
  157. print("Hello", "world", "!");
  158. print();
  159. print(3,1,4);
  160. });
  161. assertArrayEquals("Incorrect items collected.", ["Hello", "world", "!", 3, 1, 4], collected);
  162. },
  163. function collectPrintRetainsLegacyPrint() {
  164. var savedPrint = this.print;
  165. _l.collectPrint(function() {
  166. print("Hello", "world", "!");
  167. print();
  168. print(3,1,4);
  169. });
  170. assertEquals("Print not retained.", savedPrint, this.print);
  171. },
  172. function collectPrintRetainsLegacyPrintInCaseOfException() {
  173. var savedPrint = this.print;
  174. var f = function() { throw "exception"; };
  175. try {
  176. _l.collectPrint(f);
  177. fail("Exception not thrown");
  178. } catch(ex if ex === "exception") {
  179. // pass
  180. }
  181. assertEquals("Print not retained.", savedPrint, this.print);
  182. },
  183. function collectPrintRetainsLegacyPrintInCaseOfUndefinedException() {
  184. var savedPrint = this.print;
  185. var f = function() { throw undefined; };
  186. try {
  187. _l.collectPrint(f);
  188. fail("Exception not thrown");
  189. } catch(ex if ex === undefined) {
  190. // pass
  191. }
  192. assertEquals("Print not retained.", savedPrint, this.print);
  193. },
  194. function collectYieldCollectsYieldedArguments() {
  195. var collected = _l.collectYield(function() {
  196. yield "Hello";
  197. yield 3;
  198. });
  199. assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
  200. },
  201. function collectYieldCollectsYieldeesFromTryBlockAndFinallyRuns() {
  202. var frun = false;
  203. var collected = _l.collectYield(function() {
  204. try {
  205. yield "Hello";
  206. yield 3;
  207. } finally { frun = true; }
  208. });
  209. assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
  210. assertTrue("Finally did not run", frun);
  211. },
  212. function collectYieldThrowsExceptionIfThrownInF() {
  213. try {
  214. var collected = _l.collectYield(function() {
  215. yield "Hello";
  216. throw "exception";
  217. yield "world";
  218. });
  219. fail("Exception not thrown.");
  220. } catch(ex if ex === "exception") {
  221. // pass
  222. }
  223. },
  224. function collectYieldThrowsUndefinedIfThrownInF() {
  225. try {
  226. var collected = _l.collectYield(function() {
  227. yield "Hello";
  228. throw undefined;
  229. yield "world";
  230. });
  231. fail("Exception not thrown.");
  232. } catch(ex if ex === undefined) {
  233. // pass
  234. }
  235. },
  236. function collectYieldThrowsExceptionIfExceptionThrownInF_AndFinallyInFRuns() {
  237. var finallyRun = false;
  238. try {
  239. var collected = _l.collectYield(function() {
  240. try {
  241. yield "Hello";
  242. throw "exception";
  243. yield "world";
  244. } finally { finallyRun = true; }
  245. });
  246. fail("Exception not thrown.");
  247. } catch(ex if ex === "exception") {
  248. // pass
  249. }
  250. assertTrue("Finally in f did not run.", finallyRun);
  251. },
  252. function readonlyReadsCorrectlyAndIsDynamic() {
  253. var x = {hello: "world"};
  254. var rx = _l.readonly(x);
  255. assertEquals("Hello read incorrectly", "world", rx.hello);
  256. assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
  257. x.hello = "again";
  258. x.thisIsNot = "this is";
  259. assertEquals("Hello read incorrectly", "again", rx.hello);
  260. assertEquals("ThisIsNot read incorrectly", "this is", rx.thisIsNot);
  261. },
  262. function readonlyIsReadOnly() {
  263. var x = {hello: "world"};
  264. var rx = _l.readonly(x);
  265. rx.thisIsNot = "this is";
  266. delete rx.hello;
  267. assertEquals("Hello read incorrectly", "world", rx.hello);
  268. assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
  269. },
  270. function extendExtendsCorrectlyForAllCombinations() {
  271. var srcProto = { pro: "to" }
  272. var src = object(srcProto);
  273. src.a = "b"; src.c = "d";
  274. var dst = { pro: "fi", a: "bc", yes: "true" };
  275. var result = _l.extend(dst, src);
  276. assertEquals("extend did not return extended object", dst, result);
  277. assertEquals("a wasn't copied", "b", dst.a);
  278. assertEquals("c wasn't copied", "d", dst.c);
  279. assertEquals("pro was changed", "fi", dst.pro);
  280. assertEquals("yes was changed", "true", dst.yes);
  281. },
  282. function logIsEmptyForNewStub() {
  283. var f = _l.stub();
  284. assertArrayEquals("Log is not empty array", [], f.log);
  285. },
  286. function stubIsAFunction() {
  287. var f = _l.stub();
  288. assertEquals("Stub is not a function", "function", typeof f);
  289. },
  290. function stubWithoutRetvalReturnsUndefined() {
  291. var f = _l.stub();
  292. assertEquals("Stub doesn't return undefined", undefined, f(3, "hello"));
  293. },
  294. function stubWithRetvalReturnsRetval() {
  295. var f = _l.stub("world");
  296. assertEquals("Stub returns incorrect value", "world", f(3, "hello"));
  297. },
  298. function stub_CalledMultipleTimes_ReturnsAlwaysRetval_AndLogsArguments() {
  299. var f = _l.stub("hi");
  300. var r1 = f("hello", 14, "world");
  301. assertArrayEquals("Log is incorrect 1", [["hello", 14, "world"]], f.log);
  302. var r2 = f("!", f);
  303. assertArrayEquals("Log is incorrect 2", [["hello", 14, "world"], ["!", f]], f.log);
  304. f();
  305. assertArrayEquals("Log is incorrect 3", [["hello", 14, "world"], ["!", f], []], f.log);
  306. assertEquals("Stub returns incorrect value 1", "hi", r1);
  307. assertEquals("Stub returns incorrect value 2", "hi", r2);
  308. },
  309. //TODO tests for splitReplace
  310. ];
  311. // ==== Test runner ====
  312. import("lib-support/runner");
  313. runTestsByDefault();