useful.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /* appjet:version 0.1 */
  2. // (c) 2009, Herbert Vojčík
  3. // Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)
  4. /* appjet:library */
  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. /* appjet:server */
  129. import("lib-utils/jsunit");
  130. var _l = import({}, "lib-utils/useful");
  131. // ==== Test cases ====
  132. page.testSuite = [
  133. function collectPrintCollectsPrintedArguments() {
  134. var collected = _l.collectPrint(function() {
  135. print("Hello", "world", "!");
  136. print();
  137. print(3,1,4);
  138. });
  139. assertArrayEquals("Incorrect items collected.", ["Hello", "world", "!", 3, 1, 4], collected);
  140. },
  141. function collectPrintRetainsLegacyPrint() {
  142. var savedPrint = this.print;
  143. _l.collectPrint(function() {
  144. print("Hello", "world", "!");
  145. print();
  146. print(3,1,4);
  147. });
  148. assertEquals("Print not retained.", savedPrint, this.print);
  149. },
  150. function collectPrintRetainsLegacyPrintInCaseOfException() {
  151. var savedPrint = this.print;
  152. var f = function() { throw "exception"; };
  153. try {
  154. _l.collectPrint(f);
  155. fail("Exception not thrown");
  156. } catch(ex if ex === "exception") {
  157. // pass
  158. }
  159. assertEquals("Print not retained.", savedPrint, this.print);
  160. },
  161. function collectPrintRetainsLegacyPrintInCaseOfUndefinedException() {
  162. var savedPrint = this.print;
  163. var f = function() { throw undefined; };
  164. try {
  165. _l.collectPrint(f);
  166. fail("Exception not thrown");
  167. } catch(ex if ex === undefined) {
  168. // pass
  169. }
  170. assertEquals("Print not retained.", savedPrint, this.print);
  171. },
  172. function collectYieldCollectsYieldedArguments() {
  173. var collected = _l.collectYield(function() {
  174. yield "Hello";
  175. yield 3;
  176. });
  177. assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
  178. },
  179. function collectYieldCollectsYieldeesFromTryBlockAndFinallyRuns() {
  180. var frun = false;
  181. var collected = _l.collectYield(function() {
  182. try {
  183. yield "Hello";
  184. yield 3;
  185. } finally { frun = true; }
  186. });
  187. assertArrayEquals("Incorrect items collected.", ["Hello", 3], collected);
  188. assertTrue("Finally did not run", frun);
  189. },
  190. function collectYieldThrowsExceptionIfThrownInF() {
  191. try {
  192. var collected = _l.collectYield(function() {
  193. yield "Hello";
  194. throw "exception";
  195. yield "world";
  196. });
  197. fail("Exception not thrown.");
  198. } catch(ex if ex === "exception") {
  199. // pass
  200. }
  201. },
  202. function collectYieldThrowsUndefinedIfThrownInF() {
  203. try {
  204. var collected = _l.collectYield(function() {
  205. yield "Hello";
  206. throw undefined;
  207. yield "world";
  208. });
  209. fail("Exception not thrown.");
  210. } catch(ex if ex === undefined) {
  211. // pass
  212. }
  213. },
  214. function collectYieldThrowsExceptionIfExceptionThrownInF_AndFinallyInFRuns() {
  215. var finallyRun = false;
  216. try {
  217. var collected = _l.collectYield(function() {
  218. try {
  219. yield "Hello";
  220. throw "exception";
  221. yield "world";
  222. } finally { finallyRun = true; }
  223. });
  224. fail("Exception not thrown.");
  225. } catch(ex if ex === "exception") {
  226. // pass
  227. }
  228. assertTrue("Finally in f did not run.", finallyRun);
  229. },
  230. function readonlyReadsCorrectlyAndIsDynamic() {
  231. var x = {hello: "world"};
  232. var rx = _l.readonly(x);
  233. assertEquals("Hello read incorrectly", "world", rx.hello);
  234. assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
  235. x.hello = "again";
  236. x.thisIsNot = "this is";
  237. assertEquals("Hello read incorrectly", "again", rx.hello);
  238. assertEquals("ThisIsNot read incorrectly", "this is", rx.thisIsNot);
  239. },
  240. function readonlyIsReadOnly() {
  241. var x = {hello: "world"};
  242. var rx = _l.readonly(x);
  243. rx.thisIsNot = "this is";
  244. delete rx.hello;
  245. assertEquals("Hello read incorrectly", "world", rx.hello);
  246. assertUndefined("Nonexistent property is not undefined", rx.thisIsNot);
  247. },
  248. function extendExtendsCorrectlyForAllCombinations() {
  249. var srcProto = { pro: "to" }
  250. var src = object(srcProto);
  251. src.a = "b"; src.c = "d";
  252. var dst = { pro: "fi", a: "bc", yes: "true" };
  253. var result = _l.extend(dst, src);
  254. assertEquals("extend did not return extended object", dst, result);
  255. assertEquals("a wasn't copied", "b", dst.a);
  256. assertEquals("c wasn't copied", "d", dst.c);
  257. assertEquals("pro was changed", "fi", dst.pro);
  258. assertEquals("yes was changed", "true", dst.yes);
  259. },
  260. function logIsEmptyForNewStub() {
  261. var f = _l.stub();
  262. assertArrayEquals("Log is not empty array", [], f.log);
  263. },
  264. function stubIsAFunction() {
  265. var f = _l.stub();
  266. assertEquals("Stub is not a function", "function", typeof f);
  267. },
  268. function stubWithoutRetvalReturnsUndefined() {
  269. var f = _l.stub();
  270. assertEquals("Stub doesn't return undefined", undefined, f(3, "hello"));
  271. },
  272. function stubWithRetvalReturnsRetval() {
  273. var f = _l.stub("world");
  274. assertEquals("Stub returns incorrect value", "world", f(3, "hello"));
  275. },
  276. function stub_CalledMultipleTimes_ReturnsAlwaysRetval_AndLogsArguments() {
  277. var f = _l.stub("hi");
  278. var r1 = f("hello", 14, "world");
  279. assertArrayEquals("Log is incorrect 1", [["hello", 14, "world"]], f.log);
  280. var r2 = f("!", f);
  281. assertArrayEquals("Log is incorrect 2", [["hello", 14, "world"], ["!", f]], f.log);
  282. f();
  283. assertArrayEquals("Log is incorrect 3", [["hello", 14, "world"], ["!", f], []], f.log);
  284. assertEquals("Stub returns incorrect value 1", "hi", r1);
  285. assertEquals("Stub returns incorrect value 2", "hi", r2);
  286. },
  287. ];
  288. // ==== Test runner ====
  289. import("lib-utils/runner");
  290. runTestsByDefault();