Overview
Coverage100.00 SLOC9846 LOC2708 Missed0

base/array.js
Coverage100.00 SLOC668 LOC194 Missed0
  1. 1var obj = require("./object"), misc = require("./misc"), string = require("./string"), isString = string.isString, number = require("./number.js");
  2. 1var argsToArray = misc.argsToArray;
  3. 1var isArray = exports.isArray = function (obj) {
  4. 318 return Array.isArray(obj);
  5. };
  6. 1var cross = function (num, cross) {
  7. 7 var ret = cross.reduceRight(function (a, b) {
  8. 38 if (!isArray(b)) b = [b];
  9. 19 b.unshift(num)
  10. 19 a.unshift(b);
  11. 19 return a;
  12. }, []);
  13. 7 return ret;
  14. };
  15. 1var permute = function (num, cross, length) {
  16. 6 var ret = [];
  17. 6 for (var i = 0; i < cross.length; i++) {
  18. 12 ret.push([num].concat(array.rotate(cross, i)).slice(0, length));
  19. }
  20. 6 return ret;
  21. };
  22. 1var intersection = function (a, b) {
  23. 45 var ret = [];
  24. 45 if (isArray(a) && isArray(b)) {
  25. 45 if (a.length && b.length) {
  26. 35 var aOne = a[0];
  27. 35 if (b.indexOf(aOne) != -1) {
  28. 28 ret = [aOne].concat(intersection(a.slice(1), b));
  29. } else {
  30. 7 ret = intersection(a.slice(1), b);
  31. }
  32. }
  33. }
  34. 45 return ret;
  35. };
  36. 1var _sort = (function () {
  37. 1 var isAll = function (arr, test) {
  38. 12 return arr.every(test);
  39. };
  40. 1 var defaultCmp = function (a, b) {
  41. 39 return a - b;
  42. };
  43. 1 return function _sort(arr, property) {
  44. 22 var ret = [];
  45. 22 if (isArray(arr)) {
  46. 22 ret = arr.slice();
  47. 22 if (property) {
  48. 10 if (typeof property == "function") {
  49. 1 ret.sort(property);
  50. } else {
  51. 9 ret.sort(function (a, b) {
  52. 27 var aProp = a[property], bProp = b[property];
  53. 27 if (isString(aProp) && isString(bProp)) {
  54. 9 return aProp > bProp ? 1 : aProp < bProp ? -1 : 0;
  55. } else {
  56. 18 return aProp - bProp;
  57. }
  58. });
  59. }
  60. } else {
  61. 12 if (isAll(ret, isString)) {
  62. 3 ret.sort();
  63. } else {
  64. 9 ret.sort(defaultCmp);
  65. }
  66. }
  67. }
  68. 22 return ret;
  69. };
  70. })();
  71. 1var comb = exports, array;
  72. /**
  73. * @namespace Utilities for Arrays
  74. */
  75. 1comb.array = {
  76. /**@lends comb.array*/
  77. /**
  78. * converts anything to an array
  79. *
  80. * @example
  81. * comb.array.toArray({a : "b", b : "c"}) => [["a","b"], ["b","c"]];
  82. * comb.array.toArray("a") => ["a"]
  83. * comb.array.toArray(["a"]) => ["a"];
  84. * comb.array.toArray() => [];
  85. * comb.array.toArray("a", {a : "b"}) => ["a", ["a", "b"]];
  86. */
  87. toArray:function (o) {
  88. 21 var ret = [];
  89. 21 if (o != null) {
  90. 20 var args = argsToArray(arguments);
  91. 20 if (args.length == 1) {
  92. 19 if (isArray(o)) {
  93. 12 ret = o;
  94. 7 } else if (obj.isHash(o)) {
  95. 2 for (var i in o) {
  96. 3 if (o.hasOwnProperty(i)) {
  97. 3 ret.push([i, o[i]]);
  98. }
  99. }
  100. } else {
  101. 5 ret.push(o);
  102. }
  103. } else {
  104. 1 args.forEach(function (a) {
  105. 2 ret = ret.concat(array.toArray(a));
  106. })
  107. }
  108. }
  109. 21 return ret;
  110. },
  111. /**
  112. * Sums all items in an array
  113. *
  114. * @example
  115. *
  116. * comb.array.sum([1,2,3]) => 6
  117. * comb.array.sum(["A","B","C"]) => "ABC";
  118. * var d1 = new Date(1999), d2 = new Date(2000), d3 = new Date(3000);
  119. * comb.array.sum([d1,d2,d3]) => "Wed Dec 31 1969 18:00:01 GMT-0600 (CST)"
  120. * + "Wed Dec 31 1969" 18:00:02 GMT-0600 (CST)"
  121. * + "Wed Dec 31 1969 18:00:03 GMT-0600 (CST)"
  122. * comb.array.sum([{},{},{}]) => "[object Object][object Object][object Object]";
  123. *
  124. * @param {Number[]} array the array of numbers to sum
  125. */
  126. sum:function (array) {
  127. 10 array = array || [];
  128. 10 if (array.length) {
  129. 8 return array.reduce(function (a, b) {
  130. 16 return a + b;
  131. });
  132. } else {
  133. 2 return 0;
  134. }
  135. },
  136. /**
  137. * Averages an array of numbers.
  138. * @example
  139. *
  140. * comb.array.avg([1,2,3]); //2
  141. *
  142. * @param {Number[]} array - an array of numbers
  143. * @return {Number} the average of all the numbers in the array.
  144. * @throws {Error} if the array is not all numbers.
  145. */
  146. avg:function (arr) {
  147. 6 arr = arr || [];
  148. 6 if (arr.length) {
  149. 4 var sum = array.sum(arr);
  150. 4 if (number.isNumber(sum)) {
  151. 1 return sum / arr.length;
  152. } else {
  153. 3 throw new Error("Cannot average an array of non numbers.");
  154. }
  155. } else {
  156. 2 return 0;
  157. }
  158. },
  159. /**
  160. * Allows the sorting of an array based on a property name instead. This can also
  161. * act as a sort that does not change the original array.
  162. *
  163. * <b>NOTE:</b> this does not change the original array!
  164. *
  165. * @example
  166. * comb.array.sort([{a : 1}, {a : 2}, {a : -2}], "a"); //[{a : -2}, {a : 1}, {a : 2}];
  167. * @param {Array} arr the array to sort
  168. * @param {String|Function} cmp the property to sort on. Or a function used to compare.
  169. * @return {Array} a copy of the original array that is sorted.
  170. */
  171. sort:function (arr, cmp) {
  172. 8 return _sort(arr, cmp);
  173. },
  174. /**
  175. * Finds that min value of an array. If a second argument is provided and it is a function
  176. * it will be used as a comparator function. If the second argument is a string then it will be used
  177. * as a property look up on each item.
  178. *
  179. * @example
  180. * comb.array.min([{a : 1}, {a : 2}, {a : -2}], "a"); //{a : -2}
  181. * comb.array.min([{a : 1}, {a : 2}, {a : -2}], function(a,b){
  182. * return a.a - b.a
  183. * }); //{a : -2}
  184. *
  185. * @param {Array} arr the array to find the min value on
  186. * @param {String|Function} cmp the property to sort on. Or a function used to compare.
  187. * @return {*}
  188. */
  189. min:function (arr, cmp) {
  190. 7 return _sort(arr, cmp)[0];
  191. },
  192. /**
  193. * Finds that max value of an array. If a second argument is provided and it is a function
  194. * it will be used as a comparator function. If the second argument is a string then it will be used
  195. * as a property look up on each item.
  196. *
  197. * @example
  198. * comb.array.max([{a : 1}, {a : 2}, {a : -2}], "a"); //{a : 2}
  199. * comb.array.max([{a : 1}, {a : 2}, {a : -2}], function(a,b){
  200. * return a.a - b.a
  201. * }); //{a : 2}
  202. *
  203. * @param arr the array to find the max value on
  204. * @param {String|Function} cmp the property to sort on. Or a function used to compare.
  205. * @return {*} the maximum value of the array based on the provided cmp.
  206. */
  207. max:function (arr, cmp) {
  208. 7 return _sort(arr, cmp)[arr.length - 1];
  209. },
  210. /**
  211. * Finds the difference of the two arrays.
  212. *
  213. * @example
  214. *
  215. * comb.array.difference([1,2,3], [2,3]); //[1]
  216. * comb.array.difference(["a","b",3], [3]); //["a","b"]
  217. *
  218. * @param {Array} arr1 the array we are subtracting from
  219. * @param {Array} arr2 the array we are subtracting from arr1
  220. * @return {*} the difference of the arrays.
  221. */
  222. difference:function (arr1, arr2) {
  223. 5 var ret = arr1, args = array.flatten(misc.argsToArray(arguments).slice(1));
  224. 5 if (isArray(arr1)) {
  225. 5 ret = arr1.filter(function (a) {
  226. 14 return args.indexOf(a) === -1;
  227. });
  228. }
  229. 5 return ret;
  230. },
  231. /**
  232. * Removes duplicates from an array
  233. *
  234. * @example
  235. *
  236. * comb.array.removeDuplicates([1,1,1]) => [1]
  237. * comb.array.removeDuplicates([1,2,3,2]) => [1,2,3]
  238. *
  239. * @param {Aray} array the array of elements to remove duplicates from
  240. */
  241. removeDuplicates:function (arr) {
  242. 11 if (isArray(arr)) {
  243. 11 var ret = arr.reduce(function (a, b) {
  244. 47 if (a.indexOf(b) === -1) {
  245. 32 return a.concat(b);
  246. } else {
  247. 15 return a;
  248. }
  249. }, []);
  250. 11 return ret;
  251. }
  252. },
  253. unique:function (arr) {
  254. 2 return array.removeDuplicates(arr);
  255. },
  256. /**
  257. * Rotates an array the number of specified positions
  258. *
  259. * @example
  260. * var arr = ["a", "b", "c", "d"];
  261. * comb.array.rotate(arr) => ["b", "c", "d", "a"]
  262. * comb.array.rotate(arr, 2) => ["c", "d", "a", "b"]);
  263. * comb.array.rotate(arr, 3) => ["d", "a", "b", "c"]);
  264. * comb.array.rotate(arr, 4) => ["a", "b", "c", "d"]);
  265. * comb.array.rotate(arr, -1) => ["d", "a", "b", "c"]);
  266. * comb.array.rotate(arr, -2) => ["c", "d", "a", "b"]);
  267. * comb.array.rotate(arr, -3) => ["b", "c", "d", "a"]);
  268. * comb.array.rotate(arr, -4) => ["a", "b", "c", "d"]);
  269. *
  270. * @param {Array} array the array of elements to remove duplicates from
  271. * @param {Number} numberOfTimes the number of times to rotate the array
  272. */
  273. rotate:function (arr, numberOfTimes) {
  274. 58 var ret = arr.slice();
  275. 58 if (typeof numberOfTimes != "number") {
  276. 1 numberOfTimes = 1;
  277. }
  278. 58 if (numberOfTimes && isArray(arr)) {
  279. 32 if (numberOfTimes > 0) {
  280. 22 ret.push(ret.shift());
  281. 22 numberOfTimes--;
  282. } else {
  283. 10 ret.unshift(ret.pop());
  284. 10 numberOfTimes++;
  285. }
  286. 32 return array.rotate(ret, numberOfTimes);
  287. } else {
  288. 26 return ret;
  289. }
  290. },
  291. /**
  292. * Finds all permutations of an array
  293. *
  294. * @example
  295. * var arr = [1,2,3];
  296. * comb.array.permutations(arr) => [[ 1, 2, 3 ],[ 1, 3, 2 ],[ 2, 3, 1 ],
  297. * [ 2, 1, 3 ],[ 3, 1, 2 ],[ 3, 2, 1 ]]
  298. * comb.array.permutations(arr, 2) => [[ 1, 2],[ 1, 3],[ 2, 3],[ 2, 1],[ 3, 1],[ 3, 2]]
  299. * comb.array.permutations(arr, 1) => [[1],[2],[3]]
  300. * comb.array.permutations(arr, 0) => [[]]
  301. * comb.array.permutations(arr, 4) => []
  302. *
  303. * @param {Array} arr the array to permute.
  304. * @param {Number} length the number of elements to permute.
  305. */
  306. permutations:function (arr, length) {
  307. 5 var ret = [];
  308. 5 if (isArray(arr)) {
  309. 5 var copy = arr.slice(0);
  310. 5 if (typeof length != "number") {
  311. 1 length = arr.length;
  312. }
  313. 5 if (!length) {
  314. 1 ret = [
  315. []
  316. ];
  317. 4 } else if (length <= arr.length) {
  318. 3 ret = arr.reduce(function (a, b, i) {
  319. 9 if (length > 1) {
  320. 6 var ret = permute(b, array.rotate(copy, i).slice(1), length);
  321. } else {
  322. 3 ret = [
  323. [b]
  324. ];
  325. }
  326. 9 return a.concat(ret);
  327. }, [])
  328. }
  329. }
  330. 5 return ret;
  331. },
  332. /**
  333. * Zips to arrays together
  334. *
  335. * @example
  336. * var a = [ 4, 5, 6 ], b = [ 7, 8, 9 ]
  337. * comb.array.zip([1], [2], [3]) => [[ 1, 2, 3 ]]);
  338. * comb.array.zip([1,2], [2], [3]) => [[ 1, 2, 3 ],[2, null, null]]
  339. * comb.array.zip([1,2,3], a, b) => [[1, 4, 7],[2, 5, 8],[3, 6, 9]]
  340. * comb.array.zip([1,2], a, b) => [[1, 4, 7],[2, 5, 8]]
  341. * comb.array.zip(a, [1,2], [8]) => [[4,1,8],[5,2,null],[6,null,null]]
  342. *
  343. * @param arrays variable number of arrays to zip together
  344. */
  345. zip:function () {
  346. 5 var ret = [];
  347. 5 var arrs = argsToArray(arguments);
  348. 5 if (arrs.length > 1) {
  349. 5 var arr1 = arrs.shift();
  350. 5 if (isArray(arr1)) {
  351. 5 ret = arr1.reduce(function (a, b, i) {
  352. 11 var curr = [b];
  353. 11 for (var j = 0; j < arrs.length; j++) {
  354. 22 var currArr = arrs[j];
  355. 22 if (isArray(currArr) && !misc.isUndefined(currArr[i])) {
  356. 17 curr.push(currArr[i]);
  357. } else {
  358. 5 curr.push(null);
  359. }
  360. }
  361. 11 a.push(curr)
  362. 11 return a;
  363. }, []);
  364. }
  365. }
  366. 5 return ret;
  367. },
  368. /**
  369. * Transposes an array of arrays
  370. * @example
  371. *
  372. * comb.array.transpose([[1,2,3], [4,5,6]]) => [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]
  373. * comb.array.transpose([[1,2], [3,4], [5,6]]) => [ [ 1, 3, 5 ], [ 2, 4, 6 ] ]
  374. * comb.array.transpose([[1], [3,4], [5,6]]) => [[1]])
  375. *
  376. * @param [Array[Array[]]] arr Array of arrays
  377. */
  378. transpose:function (arr) {
  379. 3 var ret = [];
  380. 3 if (isArray(arr) && arr.length) {
  381. 3 var last;
  382. 3 arr.forEach(function (a) {
  383. 8 if (isArray(a) && (!last || a.length == last.length)) {
  384. 6 a.forEach(function (b, i) {
  385. 13 !ret[i] && (ret[i] = []);
  386. 13 ret[i].push(b);
  387. });
  388. 6 last = a;
  389. }
  390. });
  391. }
  392. 3 return ret;
  393. },
  394. /**
  395. * Retrieves values at specified indexes in the array
  396. *
  397. * @example
  398. *
  399. * var arr =["a", "b", "c", "d"]
  400. * comb.array.valuesAt(arr, 1,2,3) => ["b", "c", "d"];
  401. * comb.array.valuesAt(arr, 1,2,3, 4) => ["b", "c", "d", null];
  402. * comb.array.valuesAt(arr, 0,3) => ["a", "d"];
  403. *
  404. * @param {Array} arr the array to retrieve values from
  405. * @index {Number} index variable number of indexes to retrieve
  406. */
  407. valuesAt:function (arr, indexes) {
  408. 3 var ret = [];
  409. 3 var indexes = argsToArray(arguments);
  410. 3 var arr = indexes.shift(), l = arr.length;
  411. 3 if (isArray(arr) && indexes.length) {
  412. 3 for (var i = 0; i < indexes.length; i++) {
  413. 9 ret.push(arr[indexes[i]] || null);
  414. }
  415. }
  416. 3 return ret;
  417. },
  418. /**
  419. * Union a variable number of arrays together
  420. *
  421. * @example
  422. *
  423. * comb.array.union(['a','b','c'], ['b','c', 'd']) => ["a", "b", "c", "d"]
  424. * comb.array.union(["a"], ["b"], ["c"], ["d"], ["c"]) => ["a", "b", "c", "d"]
  425. *
  426. * @param arrs variable number of arrays to union
  427. */
  428. union:function () {
  429. 2 var ret = [];
  430. 2 var arrs = argsToArray(arguments);
  431. 2 if (arrs.length > 1) {
  432. 2 ret = array.removeDuplicates(arrs.reduce(function (a, b) {
  433. 7 return a.concat(b);
  434. }, []));
  435. }
  436. 2 return ret;
  437. },
  438. /**
  439. * Finds the intersection of arrays
  440. * NOTE : this function accepts an arbitrary number of arrays
  441. *
  442. * @example
  443. * comb.array.intersect([1,2], [2,3], [2,3,5]) => [2]
  444. * comb.array.intersect([1,2,3], [2,3,4,5], [2,3,5]) => [2,3]
  445. * comb.array.intersect([1,2,3,4], [2,3,4,5], [2,3,4,5]) => [2,3,4]
  446. * comb.array.intersect([1,2,3,4,5], [1,2,3,4,5], [1,2,3]) => [1,2,3]
  447. * comb.array.intersect([[1,2,3,4,5],[1,2,3,4,5],[1,2,3]]) => [1,2,3]
  448. *
  449. * @param {Array} a
  450. * @param {Array} b
  451. */
  452. intersect:function (a, b) {
  453. 5 var collect = [], set;
  454. 5 var args = argsToArray(arguments);
  455. 5 if (args.length > 1) {
  456. //assume we are intersections all the lists in the array
  457. 4 set = args;
  458. } else {
  459. 1 set = args[0];
  460. }
  461. 5 if (isArray(set)) {
  462. 5 var x = set.shift();
  463. 5 var collect = set.reduce(function (a, b) {
  464. 10 return intersection(a, b);
  465. }, x);
  466. }
  467. 5 return array.removeDuplicates(collect);
  468. },
  469. /**
  470. * Finds the powerset of an array
  471. *
  472. * @example
  473. *
  474. * comb.array.powerSet([1,2]) => [
  475. * [],
  476. * [ 1 ],
  477. * [ 2 ],
  478. * [ 1, 2 ]
  479. * ]
  480. * comb.array.powerSet([1,2,3]) => [
  481. * [],
  482. * [ 1 ],
  483. * [ 2 ],
  484. * [ 1, 2 ],
  485. * [ 3 ],
  486. * [ 1, 3 ],
  487. * [ 2, 3 ],
  488. * [ 1, 2, 3 ]
  489. * ]
  490. * comb.array.powerSet([1,2,3,4]) => [
  491. * [],
  492. * [ 1 ],
  493. * [ 2 ],
  494. * [ 1, 2 ],
  495. * [ 3 ],
  496. * [ 1, 3 ],
  497. * [ 2, 3 ],
  498. * [ 1, 2, 3 ],
  499. * [ 4 ],
  500. * [ 1, 4 ],
  501. * [ 2, 4 ],
  502. * [ 1, 2, 4 ],
  503. * [ 3, 4 ],
  504. * [ 1, 3, 4 ],
  505. * [ 2, 3, 4 ],
  506. * [ 1, 2, 3, 4 ]
  507. * ]
  508. *
  509. * @param {Array} arr the array to find the powerset of
  510. */
  511. powerSet:function (arr) {
  512. 3 var ret = [];
  513. 3 if (isArray(arr) && arr.length) {
  514. 3 var ret = arr.reduce(function (a, b) {
  515. 9 var ret = a.map(function (c) {
  516. 25 return c.concat(b);
  517. })
  518. 9 return a.concat(ret);
  519. }, [
  520. []
  521. ]);
  522. }
  523. 3 return ret;
  524. },
  525. /**
  526. * Find the cartesian product of two arrays
  527. *
  528. * @example
  529. *
  530. * comb.array.cartesian([1,2], [2,3]) => [
  531. * [1,2],
  532. * [1,3],
  533. * [2,2],
  534. * [2,3]
  535. * ]
  536. * comb.array.cartesian([1,2], [2,3,4]) => [
  537. * [1,2],
  538. * [1,3],
  539. * [1,4] ,
  540. * [2,2],
  541. * [2,3],
  542. * [2,4]
  543. * ]
  544. * comb.array.cartesian([1,2,3], [2,3,4]) => [
  545. * [1,2],
  546. * [1,3],
  547. * [1,4] ,
  548. * [2,2],
  549. * [2,3],
  550. * [2,4] ,
  551. * [3,2],
  552. * [3,3],
  553. * [3,4]
  554. * ]
  555. *
  556. * @param {Array} a
  557. * @param {Array} b
  558. */
  559. cartesian:function (a, b) {
  560. 10 var ret = [];
  561. 10 if (isArray(a) && isArray(b) && a.length && b.length)
  562. 7 ret = cross(a[0], b).concat(array.cartesian(a.slice(1), b));
  563. 10 return ret;
  564. },
  565. /**
  566. * Compacts an array removing null or undefined objects from the array.
  567. *
  568. * @example
  569. *
  570. * var x;
  571. * comb.array.compact([1,null,null,x,2]) => [1,2]
  572. * comb.array.compact([1,2]) => [1,2]
  573. *
  574. * @param {Array} arr
  575. */
  576. compact:function (arr) {
  577. 2 var ret = [];
  578. 2 if (isArray(arr) && arr.length) {
  579. 2 ret = arr.filter(function (item) {
  580. 7 return !misc.isUndefinedOrNull(item);
  581. })
  582. }
  583. 2 return ret;
  584. },
  585. /**
  586. * Creates a new array that is the result of the array concated the number of
  587. * times. If times is not specified or it equals 0 then it defaults to 1.
  588. * @param {Array} arr the array to multiply.
  589. * @param {Number} [times=1] the number of times to multiple the array.
  590. * @return {Array} a new array that is the result of the array multiplied the number of times specified.
  591. */
  592. multiply:function (arr, times) {
  593. 4 times = number.isNumber(times) ? times : 1;
  594. 4 if (!times) {
  595. //make sure times is greater than zero if it is zero then dont multiply it
  596. 1 times = 1;
  597. }
  598. 4 arr = array.toArray(arr || []);
  599. 4 var ret = [], i = 0;
  600. 4 while (++i <= times) {
  601. 5 ret = ret.concat(arr);
  602. }
  603. 4 return ret;
  604. },
  605. /**
  606. * Flatten multiple arrays into a single array
  607. *
  608. * @example
  609. *
  610. * comb.array.flatten([1,2], [2,3], [3,4]) => [1,2,2,3,3,4]
  611. * comb.array.flatten([1,"A"], [2,"B"], [3,"C"]) => [1,"A",2,"B",3,"C"]
  612. *
  613. * @param array
  614. */
  615. flatten:function (arr) {
  616. 10 var set;
  617. 10 var args = argsToArray(arguments);
  618. 10 if (args.length > 1) {
  619. //assume we are intersections all the lists in the array
  620. 3 set = args;
  621. } else {
  622. 7 set = array.toArray(arr);
  623. }
  624. 10 return set.reduce(function (a, b) {
  625. 19 return a.concat(b);
  626. }, []);
  627. }
  628. }
  629. ;
  630. 1array = comb.array;
base/broadcast.js
Coverage100.00 SLOC171 LOC63 Missed0
  1. 1var func = require("./functions"),
  2. obj = require("./object");
  3. 1var comb = exports;
  4. 1var wrapper = function() {
  5. 8 return function() {
  6. 31 var c = arguments.callee, listeners = c.__listeners, func = c.func, r;
  7. 31 if (func) {
  8. 31 r = func.apply(this, arguments);
  9. }
  10. 31 for (var i = 0; i < listeners.length; i++) {
  11. 39 var lis = listeners[i];
  12. 39 if (lis) {
  13. 30 lis.apply(this, arguments);
  14. }
  15. }
  16. 31 return r;
  17. }
  18. };
  19. 1var listeners = {};
  20. 1obj.merge(comb, {
  21. /**@lends comb*/
  22. /**
  23. * Disconnects a listener to a function
  24. * @param {handle} A handle returned from comb.connect
  25. */
  26. disconnect : function(handle) {
  27. 10 if (handle && handle.length == 3) {
  28. 9 var obj = handle[0], method = handle[1], cb = handle[2];
  29. 9 if (typeof method != "string") throw "comb.disconnect : When calling disconnect the method must be string";
  30. 9 var scope = obj || global, ls;
  31. 9 if (typeof scope[method] == "function") {
  32. 8 ls = scope[method].__listeners;
  33. 8 if (ls && cb-- > 0) {
  34. //we dont want to splice it because our indexing will get off
  35. 8 ls[cb] = null;
  36. }
  37. } else {
  38. 1 throw new Error("unknown method " + method + " in object " + obj);
  39. }
  40. } else {
  41. 1 throw "comb.disconnect : invalid handle"
  42. }
  43. },
  44. /**
  45. * Function to listen when other functions are called
  46. *
  47. * @example
  48. *
  49. * comb.connect(obj, "event", myfunc);
  50. * comb.connect(obj, "event", "log", console);
  51. *
  52. * @param {Object} obj the object in which the method you are connecting to resides
  53. * @param {String} method the name of the method to connect to
  54. * @param {Function} cb the function to callback
  55. * @param {Object} [scope] the scope to call the specified cb in
  56. *
  57. * @returns {Array} handle to pass to {@link comb.disconnect}
  58. */
  59. connect : function(obj, method, cb, scope) {
  60. 14 var index;
  61. 14 if (typeof method != "string") throw new Error("When calling connect the method must be string");
  62. 14 if (!func.isFunction(cb)) throw new Error("When calling connect callback must be a string");
  63. 14 var scope = obj || global, listeners, newMethod;
  64. 14 if (typeof scope[method] == "function") {
  65. 13 listeners = scope[method].__listeners;
  66. 13 if (!listeners) {
  67. 8 newMethod = wrapper();
  68. 8 newMethod.func = obj[method];
  69. 8 listeners = (newMethod.__listeners = []);
  70. 8 scope[method] = newMethod;
  71. }
  72. 13 index = listeners.push(cb);
  73. } else {
  74. 1 throw new Error("unknow method " + method + " in object " + obj);
  75. }
  76. 13 return [obj, method, index];
  77. },
  78. /**
  79. * Broadcasts an event to all listeners
  80. * NOTE : the function takes a variable number of arguments
  81. * i.e. all arguments after the topic will be passed to the listeners
  82. *
  83. * @example
  84. *
  85. *
  86. * comb.broadcast("hello", "hello world");
  87. * //the args "hello" and "world" will be passed to any listener of the topic
  88. * //"hello"
  89. * comb.broadcast("hello", "hello", "world");
  90. *
  91. * @param {String} topic the topic to brodcast
  92. * @param params the information to bradcast
  93. */
  94. broadcast : function() {
  95. 4 var args = Array.prototype.slice.call(arguments);
  96. 4 var topic = args.splice(0, 1)[0];
  97. 4 if (topic) {
  98. 4 var list = listeners[topic];
  99. 4 if (list) {
  100. 4 for (var i = list.length - 1; i >= 0; i--) {
  101. 4 var han = list[i], cb = han.cb;
  102. 4 if (cb) {
  103. 4 cb.apply(this, args);
  104. }
  105. }
  106. }
  107. }
  108. },
  109. /**
  110. * Listen for the broadcast of certain events
  111. *
  112. * @example
  113. * comb.listen("hello", function(arg1, arg2){
  114. * console.log(arg1);
  115. * console.log(arg2);
  116. * });
  117. *
  118. * @param {String} topic the topic to listen for
  119. * @param {Function} callback the funciton to call when the topic is published
  120. *
  121. * @returns a handle to pass to {@link comb.unListen}
  122. */
  123. listen : function(topic, callback) {
  124. 3 if (!func.isFunction(callback)) throw new Error("callback must be a function");
  125. 3 var handle = {
  126. topic : topic,
  127. cb : callback,
  128. pos : null
  129. };
  130. 3 var list = listeners[topic];
  131. 3 if (!list) {
  132. 3 list = (listeners[topic] = []);
  133. }
  134. 3 list.push(handle);
  135. 3 handle.pos = list.length - 1;
  136. 3 return handle;
  137. },
  138. /**
  139. * Disconnects a listener
  140. *
  141. * @param handle a handle returned from {@link comb.listen}
  142. */
  143. unListen : function(handle) {
  144. 2 if (handle) {
  145. 2 var topic = handle.topic, list = listeners[topic];
  146. 2 if (list) {
  147. 2 for (var i = list.length - 1; i >= 0; i--) {
  148. 2 if (list[i] == handle) {
  149. 2 list.splice(i, 1);
  150. }
  151. }
  152. 2 if (!list.length) {
  153. 2 delete listeners[topic];
  154. }
  155. }
  156. }
  157. }
  158. });
base/date.js
Coverage100.00 SLOC1106 LOC356 Missed0
  1. 1var string = require("./string").string;
  2. /**
  3. * @ignore
  4. * Based on DOJO Date Implementation
  5. *
  6. * Dojo is available under *either* the terms of the modified BSD license *or* the
  7. * Academic Free License version 2.1. As a recipient of Dojo, you may choose which
  8. * license to receive this code under (except as noted in per-module LICENSE
  9. * files). Some modules may not be the copyright of the Dojo Foundation. These
  10. * modules contain explicit declarations of copyright in both the LICENSE files in
  11. * the directories in which they reside and in the code itself. No external
  12. * contributions are allowed under licenses which are fundamentally incompatible
  13. * with the AFL or BSD licenses that Dojo is distributed under.
  14. *
  15. */
  16. 1var floor = Math.floor, round = Math.round, min = Math.min, pow = Math.pow, ceil = Math.ceil, abs = Math.abs;
  17. 1var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  18. 1var monthAbbr = ["Jan.", "Feb.", "Mar.", "Apr.", "May.", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec."];
  19. 1var monthLetter = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
  20. 1var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
  21. 1var dayAbbr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  22. 1var dayLetter = ["S", "M", "T", "W", "T", "F", "S"];
  23. 1var eraNames = ["Before Christ", "Anno Domini"];
  24. 1var eraAbbr = ["BC", "AD"];
  25. 1var eraLetter = ["B", "A"];
  26. 1var comb = exports, date;
  27. /**
  28. * Determines if obj is a Date
  29. *
  30. * @param {Anything} obj the thing to test if it is a Date
  31. * @memberOf comb
  32. * @returns {Boolean} true if it is a Date false otherwise
  33. */
  34. 1comb.isDate = function (obj) {
  35. 36 var undef;
  36. 36 return (obj !== undef && typeof obj === "object" && obj instanceof Date);
  37. };
  38. 1function getDayOfYear(/*Date*/dateObject, utc) {
  39. // summary: gets the day of the year as represented by dateObject
  40. 6 return date.difference(new Date(dateObject.getFullYear(), 0, 1, dateObject.getHours()), dateObject, null, utc) + 1; // Number
  41. }
  42. 1function getWeekOfYear(/*Date*/dateObject, /*Number*/firstDayOfWeek, utc) {
  43. 4 firstDayOfWeek = firstDayOfWeek || 0;
  44. 4 var fullYear = dateObject[utc ? "getUTCFullYear" : "getFullYear"]();
  45. 4 var firstDayOfYear = new Date(fullYear, 0, 1).getDay(),
  46. adj = (firstDayOfYear - firstDayOfWeek + 7) % 7,
  47. week = floor((getDayOfYear(dateObject) + adj - 1) / 7);
  48. // if year starts on the specified day, start counting weeks at 1
  49. 4 if (firstDayOfYear == firstDayOfWeek) {
  50. 4 week++;
  51. }
  52. 4 return week; // Number
  53. }
  54. 1function buildDateEXP(pattern, tokens) {
  55. 28 return pattern.replace(/([a-z])\1*/ig,
  56. function (match) {
  57. // Build a simple regexp. Avoid captures, which would ruin the tokens list
  58. 100 var s,
  59. c = match.charAt(0),
  60. l = match.length,
  61. p2 = '', p3 = '';
  62. 100 p2 = '0?';
  63. 100 p3 = '0{0,2}';
  64. 100 switch (c) {
  65. case 'y':
  66. 17 s = '\\d{2,4}';
  67. 17 break;
  68. case 'M':
  69. 17 s = (l > 2) ? '\\S+?' : '1[0-2]|' + p2 + '[1-9]';
  70. 17 break;
  71. case 'D':
  72. 2 s = '[12][0-9][0-9]|3[0-5][0-9]|36[0-6]|' + p3 + '[1-9][0-9]|' + p2 + '[1-9]';
  73. 2 break;
  74. case 'd':
  75. 16 s = '3[01]|[12]\\d|' + p2 + '[1-9]';
  76. 16 break;
  77. case 'w':
  78. 2 s = '[1-4][0-9]|5[0-3]|' + p2 + '[1-9]';
  79. 2 break;
  80. case 'E':
  81. 6 s = '\\S+';
  82. 6 break;
  83. case 'h': //hour (1-12)
  84. 5 s = '1[0-2]|' + p2 + '[1-9]';
  85. 5 break;
  86. case 'K': //hour (1-24)
  87. 2 s = '1[01]|' + p2 + '\\d';
  88. 2 break;
  89. case 'H': //hour (0-23)
  90. 1 s = '1\\d|2[0-3]|' + p2 + '\\d';
  91. 1 break;
  92. case 'k': //hour (1-24)
  93. 1 s = '1\\d|2[0-4]|' + p2 + '[1-9]';
  94. 1 break;
  95. case 'm':
  96. case 's':
  97. 15 s = '[0-5]\\d';
  98. 15 break;
  99. case 'S':
  100. 5 s = '\\d{' + l + '}';
  101. 5 break;
  102. case 'a':
  103. 3 var am = 'AM', pm = 'PM';
  104. 3 s = am + '|' + pm;
  105. 3 if (am != am.toLowerCase()) {
  106. 3 s += '|' + am.toLowerCase();
  107. }
  108. 3 if (pm != pm.toLowerCase()) {
  109. 3 s += '|' + pm.toLowerCase();
  110. }
  111. 3 s = s.replace(/\./g, "\\.");
  112. 3 break;
  113. case 'v':
  114. case 'z':
  115. case 'Z':
  116. case 'G':
  117. case 'q':
  118. case 'Q':
  119. 6 s = ".*";
  120. 6 break;
  121. default:
  122. 2 s = c == " " ? "\\s*" : c + "*";
  123. // console.log("parse of date format, pattern=" + pattern);
  124. }
  125. 100 if (tokens) {
  126. 100 tokens.push(match);
  127. }
  128. 100 return "(" + s + ")"; // add capture
  129. }).replace(/[\xa0 ]/g, "[\\s\\xa0]"); // normalize whitespace. Need explicit handling of \xa0 for IE.
  130. }
  131. /**
  132. * @namespace Utilities for Dates
  133. */
  134. 1comb.date = {
  135. /**@lends comb.date*/
  136. /**
  137. * Returns the number of days in the month of a date
  138. *
  139. * @example
  140. *
  141. * comb.date.getDaysInMonth(new Date(2006, 1, 1)); //28
  142. * comb.date.getDaysInMonth(new Date(2004, 1, 1)); //29
  143. * comb.date.getDaysInMonth(new Date(2006, 2, 1)); //31
  144. * comb.date.getDaysInMonth(new Date(2006, 3, 1)); //30
  145. * comb.date.getDaysInMonth(new Date(2006, 4, 1)); //31
  146. * comb.date.getDaysInMonth(new Date(2006, 5, 1)); //30
  147. * comb.date.getDaysInMonth(new Date(2006, 6, 1)); //31
  148. * @param {Date} dateObject the date containing the month
  149. * @return {Number} the number of days in the month
  150. */
  151. getDaysInMonth:function (/*Date*/dateObject) {
  152. // summary:
  153. // Returns the number of days in the month used by dateObject
  154. 18 var month = dateObject.getMonth();
  155. 18 var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  156. 18 if (month == 1 && date.isLeapYear(dateObject)) {
  157. 3 return 29;
  158. } // Number
  159. 15 return days[month]; // Number
  160. },
  161. /**
  162. * Determines if a date is a leap year
  163. *
  164. * @example
  165. *
  166. * comb.date.isLeapYear(new Date(1600, 0, 1)); //true
  167. * comb.date.isLeapYear(new Date(2004, 0, 1)); //true
  168. * comb.date.isLeapYear(new Date(2000, 0, 1)); //true
  169. * comb.date.isLeapYear(new Date(2006, 0, 1)); //false
  170. * comb.date.isLeapYear(new Date(1900, 0, 1)); //false
  171. * comb.date.isLeapYear(new Date(1800, 0, 1)); //false
  172. * comb.date.isLeapYear(new Date(1700, 0, 1)); //false
  173. *
  174. * @param {Date} dateObject
  175. * @returns {Boolean} true if it is a leap year false otherwise
  176. */
  177. isLeapYear:function (/*Date*/dateObject, utc) {
  178. 14 var year = dateObject[utc ? "getUTCFullYear" : "getFullYear"]();
  179. 14 return !(year % 400) || (!(year % 4) && !!(year % 100)); // Boolean
  180. },
  181. /**
  182. * Determines if a date is on a weekend
  183. *
  184. * @example
  185. *
  186. * var thursday = new Date(2006, 8, 21);
  187. * var saturday = new Date(2006, 8, 23);
  188. * var sunday = new Date(2006, 8, 24);
  189. * var monday = new Date(2006, 8, 25);
  190. * comb.date.isWeekend(thursday)); //false
  191. * comb.date.isWeekend(saturday); //true
  192. * comb.date.isWeekend(sunday); //true
  193. * comb.date.isWeekend(monday)); //false
  194. *
  195. * @param {Date} dateObject the date to test
  196. *
  197. * @returns {Boolean} true if the date is a weekend
  198. */
  199. isWeekend:function (/*Date?*/dateObject, utc) {
  200. // summary:
  201. // Determines if the date falls on a weekend, according to local custom.
  202. 4 var day = (dateObject || new Date())[utc ? "getUTCDay" : "getDay"]();
  203. 4 return day == 0 || day == 6;
  204. },
  205. /**
  206. * Get the timezone of a date
  207. *
  208. * @example
  209. * //just setting the strLocal to simulate the toString() of a date
  210. * dt.str = 'Sun Sep 17 2006 22:25:51 GMT-0500 (CDT)';
  211. * //just setting the strLocal to simulate the locale
  212. * dt.strLocale = 'Sun 17 Sep 2006 10:25:51 PM CDT';
  213. * comb.date.getTimezoneName(dt); //'CDT'
  214. * dt.str = 'Sun Sep 17 2006 22:57:18 GMT-0500 (CDT)';
  215. * dt.strLocale = 'Sun Sep 17 22:57:18 2006';
  216. * comb.date.getTimezoneName(dt); //'CDT'
  217. * @param dateObject the date to get the timezone from
  218. *
  219. * @returns {String} the timezone of the date
  220. */
  221. getTimezoneName:function (/*Date*/dateObject) {
  222. 33 var str = dateObject.toString();
  223. 33 var tz = '';
  224. 33 var pos = str.indexOf('(');
  225. 33 if (pos > -1) {
  226. 29 tz = str.substring(++pos, str.indexOf(')'));
  227. }
  228. 33 return tz; // String
  229. },
  230. /**
  231. * Compares two dates
  232. *
  233. * @example
  234. *
  235. * var d1 = new Date();
  236. * d1.setHours(0);
  237. * comb.date.compare(d1, d1); // 0
  238. *
  239. * var d1 = new Date();
  240. * d1.setHours(0);
  241. * var d2 = new Date();
  242. * d2.setFullYear(2005);
  243. * d2.setHours(12);
  244. * comb.date.compare(d1, d2, "date"); // 1
  245. * comb.date.compare(d1, d2, "datetime"); // 1
  246. *
  247. * var d1 = new Date();
  248. * d1.setHours(0);
  249. * var d2 = new Date();
  250. * d2.setFullYear(2005);
  251. * d2.setHours(12);
  252. * comb.date.compare(d2, d1, "date"); // -1
  253. * comb.date.compare(d1, d2, "time"); //-1
  254. *
  255. * @param {Date|String} date1 the date to comapare
  256. * @param {Date|String} [date2=new Date()] the date to compare date1 againse
  257. * @param {"date"|"time"|"datetime"} portion compares the portion specified
  258. *
  259. * @returns -1 if date1 is < date2 0 if date1 === date2 1 if date1 > date2
  260. */
  261. compare:function (/*Date*/date1, /*Date*/date2, /*String*/portion) {
  262. 5 date1 = new Date(date1);
  263. 5 date2 = new Date((date2 || new Date()));
  264. 5 if (portion == "date") {
  265. // Ignore times and compare dates.
  266. 2 date1.setHours(0, 0, 0, 0);
  267. 2 date2.setHours(0, 0, 0, 0);
  268. 3 } else if (portion == "time") {
  269. // Ignore dates and compare times.
  270. 1 date1.setFullYear(0, 0, 0);
  271. 1 date2.setFullYear(0, 0, 0);
  272. }
  273. 5 var ret = 0;
  274. 5 date1 > date2 && (ret = 1);
  275. 5 date1 < date2 && (ret = -1);
  276. 5 return ret; // int
  277. },
  278. /**
  279. * Adds a specified interval and amount to a date
  280. *
  281. * @example
  282. * var dtA = new Date(2005, 11, 27);
  283. * comb.date.add(dtA, "year", 1); //new Date(2006, 11, 27);
  284. * comb.date.add(dtA, "years", 1); //new Date(2006, 11, 27);
  285. *
  286. * dtA = new Date(2000, 0, 1);
  287. * comb.date.add(dtA, "quarter", 1); //new Date(2000, 3, 1);
  288. * comb.date.add(dtA, "quarters", 1); //new Date(2000, 3, 1);
  289. *
  290. * dtA = new Date(2000, 0, 1);
  291. * comb.date.add(dtA, "month", 1); //new Date(2000, 1, 1);
  292. * comb.date.add(dtA, "months", 1); //new Date(2000, 1, 1);
  293. *
  294. * dtA = new Date(2000, 0, 31);
  295. * comb.date.add(dtA, "month", 1); //new Date(2000, 1, 29);
  296. * comb.date.add(dtA, "months", 1); //new Date(2000, 1, 29);
  297. *
  298. * dtA = new Date(2000, 0, 1);
  299. * comb.date.add(dtA, "week", 1); //new Date(2000, 0, 8);
  300. * comb.date.add(dtA, "weeks", 1); //new Date(2000, 0, 8);
  301. *
  302. * dtA = new Date(2000, 0, 1);
  303. * comb.date.add(dtA, "day", 1); //new Date(2000, 0, 2);
  304. *
  305. * dtA = new Date(2000, 0, 1);
  306. * comb.date.add(dtA, "weekday", 1); //new Date(2000, 0, 3);
  307. *
  308. * dtA = new Date(2000, 0, 1, 11);
  309. * comb.date.add(dtA, "hour", 1); //new Date(2000, 0, 1, 12);
  310. *
  311. * dtA = new Date(2000, 11, 31, 23, 59);
  312. * comb.date.add(dtA, "minute", 1); //new Date(2001, 0, 1, 0, 0);
  313. *
  314. * dtA = new Date(2000, 11, 31, 23, 59, 59);
  315. * comb.date.add(dtA, "second", 1); //new Date(2001, 0, 1, 0, 0, 0);
  316. *
  317. * dtA = new Date(2000, 11, 31, 23, 59, 59, 999);
  318. * comb.date.add(dtA, "millisecond", 1); //new Date(2001, 0, 1, 0, 0, 0, 0);
  319. *
  320. * @param {Date} date
  321. * @param {String} interval the interval to add
  322. * <ul>
  323. * <li>day | days</li>
  324. * <li>weekday | weekdays</li>
  325. * <li>year | years</li>
  326. * <li>week | weeks</li>
  327. * <li>quarter | quarters</li>
  328. * <li>months | months</li>
  329. * <li>hour | hours</li>
  330. * <li>minute | minutes</li>
  331. * <li>second | seconds</li>
  332. * <li>millisecond | milliseconds</li>
  333. * </ul>
  334. * @param {Number} [amount=0] the amount to add
  335. */
  336. add:function (/*Date*/date, /*String*/interval, /*int*/amount) {
  337. 126 amount = amount | 0
  338. 126 var sum = new Date(date);
  339. 126 var fixOvershoot = false;
  340. 126 var property = "Date";
  341. //noinspection FallthroughInSwitchStatementJS
  342. 126 switch (interval) {
  343. case "day":
  344. case "days" :
  345. 33 break;
  346. case "weekday":
  347. case "weekdays":
  348. // Divide the increment time span into weekspans plus leftover days
  349. // e.g., 8 days is one 5-day weekspan / and two leftover days
  350. // Can't have zero leftover days, so numbers divisible by 5 get
  351. // a days value of 5, and the remaining days make up the number of weeks
  352. 16 var days, weeks, mod = amount % 5, strt = date.getDay(), adj = 0;
  353. 16 if (!mod) {
  354. 6 days = (amount > 0) ? 5 : -5;
  355. 6 weeks = (amount > 0) ? ((amount - 5) / 5) : ((amount + 5) / 5);
  356. } else {
  357. 10 days = mod;
  358. 10 weeks = parseInt(amount / 5);
  359. }
  360. 16 if (strt == 6 && amount > 0) {
  361. 2 adj = 1;
  362. 14 } else if (strt == 0 && amount < 0) {
  363. // Orig date is Sun / negative increment
  364. // Jump back over Sat
  365. 4 adj = -1;
  366. }
  367. // Get weekday val for the new date
  368. 16 var trgt = strt + days;
  369. // New date is on Sat or Sun
  370. 16 if (trgt == 0 || trgt == 6) {
  371. 2 adj = (amount > 0) ? 2 : -2;
  372. }
  373. // Increment by number of weeks plus leftover days plus
  374. // weekend adjustments
  375. 16 amount = (7 * weeks) + days + adj;
  376. 16 break;
  377. case "year":
  378. case "years":
  379. 20 property = "FullYear";
  380. 20 fixOvershoot = true;
  381. 20 break;
  382. case "week":
  383. case "weeks":
  384. 3 amount *= 7;
  385. 3 break;
  386. case "quarter":
  387. case "quarters" :
  388. // Naive quarter is just three months
  389. 9 amount *= 3;
  390. case "month":
  391. case "months":
  392. // Reset to last day of month if you overshoot
  393. 22 fixOvershoot = true;
  394. 22 property = "Month";
  395. 22 break;
  396. default:
  397. 32 interval = interval.replace(/s$/, "");
  398. 32 property = "UTC" + interval.charAt(0).toUpperCase() + interval.substring(1) + "s";
  399. }
  400. 126 if (property) {
  401. 126 sum["set" + property](sum["get" + property]() + amount);
  402. }
  403. 126 if (fixOvershoot && (sum.getDate() < date.getDate())) {
  404. 13 sum.setDate(0);
  405. }
  406. 126 return sum; // Date
  407. },
  408. /**
  409. * Finds the difference between two dates based on the specified interval
  410. *
  411. * @example
  412. *
  413. * var dtA, dtB;
  414. *
  415. * dtA = new Date(2005, 11, 27);
  416. * dtB = new Date(2006, 11, 27);
  417. * comb.date.difference(dtA, dtB, "year"); //1
  418. *
  419. * dtA = new Date(2000, 1, 29);
  420. * dtB = new Date(2001, 2, 1);
  421. * comb.date.difference(dtA, dtB, "quarter"); //4
  422. * comb.date.difference(dtA, dtB, "month"); //13
  423. *
  424. * dtA = new Date(2000, 1, 1);
  425. * dtB = new Date(2000, 1, 8);
  426. * comb.date.difference(dtA, dtB, "week"); //1
  427. *
  428. * dtA = new Date(2000, 1, 29);
  429. * dtB = new Date(2000, 2, 1);
  430. * comb.date.difference(dtA, dtB, "day"); //1
  431. *
  432. * dtA = new Date(2006, 7, 3);
  433. * dtB = new Date(2006, 7, 11);
  434. * comb.date.difference(dtA, dtB, "weekday"); //6
  435. *
  436. * dtA = new Date(2000, 11, 31, 23);
  437. * dtB = new Date(2001, 0, 1, 0);
  438. * comb.date.difference(dtA, dtB, "hour"); //1
  439. *
  440. * dtA = new Date(2000, 11, 31, 23, 59);
  441. * dtB = new Date(2001, 0, 1, 0, 0);
  442. * comb.date.difference(dtA, dtB, "minute"); //1
  443. *
  444. * dtA = new Date(2000, 11, 31, 23, 59, 59);
  445. * dtB = new Date(2001, 0, 1, 0, 0, 0);
  446. * comb.date.difference(dtA, dtB, "second"); //1
  447. *
  448. * dtA = new Date(2000, 11, 31, 23, 59, 59, 999);
  449. * dtB = new Date(2001, 0, 1, 0, 0, 0, 0);
  450. * comb.date.difference(dtA, dtB, "millisecond"); //1
  451. *
  452. *
  453. * @param {Date} date1
  454. * @param {Date} [date2 = new Date()]
  455. * @param {String} [interval = "day"] the intercal to find the difference of.
  456. * <ul>
  457. * <li>day | days</li>
  458. * <li>weekday | weekdays</li>
  459. * <li>year | years</li>
  460. * <li>week | weeks</li>
  461. * <li>quarter | quarters</li>
  462. * <li>months | months</li>
  463. * <li>hour | hours</li>
  464. * <li>minute | minutes</li>
  465. * <li>second | seconds</li>
  466. * <li>millisecond | milliseconds</li>
  467. * </ul>
  468. */
  469. difference:function (/*Date*/date1, /*Date?*/date2, /*String*/interval, utc) {
  470. 172 date2 = date2 || new Date();
  471. 172 interval = interval || "day";
  472. 172 var yearDiff = date2.getFullYear() - date1.getFullYear();
  473. 172 var delta = 1; // Integer return value
  474. 172 switch (interval) {
  475. case "quarter":
  476. case "quarters":
  477. 5 var m1 = date1[utc ? "getUTCMonth" : "getMonth"]();
  478. 5 var m2 = date2[utc ? "getUTCMonth" : "getMonth"]();
  479. // Figure out which quarter the months are in
  480. 5 var q1 = floor(m1 / 3) + 1;
  481. 5 var q2 = floor(m2 / 3) + 1;
  482. // Add quarters for any year difference between the dates
  483. 5 q2 += (yearDiff * 4);
  484. 5 delta = q2 - q1;
  485. 5 break;
  486. case "weekday":
  487. case "weekdays":
  488. 28 var days = round(date.difference(date1, date2, "day", utc));
  489. 28 var weeks = parseInt(date.difference(date1, date2, "week", utc));
  490. 28 var mod = days % 7;
  491. // Even number of weeks
  492. 28 if (mod == 0) {
  493. 7 days = weeks * 5;
  494. } else {
  495. // Weeks plus spare change (< 7 days)
  496. 21 var adj = 0;
  497. 21 var aDay = date1[utc ? "getUTCDay" : "getDay"]();
  498. 21 var bDay = date2[utc ? "getUTCDay" : "getDay"]();
  499. 21 weeks = parseInt(days / 7);
  500. 21 mod = days % 7;
  501. // Mark the date advanced by the number of
  502. // round weeks (may be zero)
  503. 21 var dtMark = new Date(date1);
  504. 21 dtMark.setDate(dtMark[utc ? "getUTCDate" : "getDate"]() + (weeks * 7));
  505. 21 var dayMark = dtMark[utc ? "getUTCDay" : "getDay"]();
  506. // Spare change days -- 6 or less
  507. 21 if (days > 0) {
  508. 12 switch (true) {
  509. // Range starts on Sat
  510. case (aDay == 6 || bDay == 6):
  511. 3 adj = -1;
  512. 3 break;
  513. // Range starts on Sun
  514. case aDay == 0:
  515. 4 adj = 0;
  516. 4 break;
  517. // Range ends on Sun
  518. case bDay == 0:
  519. 1 adj = -2;
  520. 1 break;
  521. // Range contains weekend
  522. case (dayMark + mod) > 5:
  523. 1 adj = -2;
  524. }
  525. 9 } else if (days < 0) {
  526. 9 switch (true) {
  527. // Range starts on Sat
  528. case aDay == 6:
  529. 1 adj = 0;
  530. 1 break;
  531. // Range starts on Sun
  532. case aDay == 0:
  533. 3 adj = 1;
  534. 3 break;
  535. // Range ends on Sat
  536. case bDay == 6:
  537. 1 adj = 2;
  538. 1 break;
  539. // Range ends on Sun
  540. case bDay == 0:
  541. 1 adj = 1;
  542. 1 break;
  543. // Range contains weekend
  544. case (dayMark + mod) < 0:
  545. 1 adj = 2;
  546. }
  547. }
  548. 21 days += adj;
  549. 21 days -= (weeks * 2);
  550. }
  551. 28 delta = days;
  552. 28 break;
  553. case "year":
  554. case "years":
  555. 8 delta = yearDiff;
  556. 8 break;
  557. case "month":
  558. case "months":
  559. 5 var m1 = date1[utc ? "getUTCMonth" : "getMonth"]();
  560. 5 var m2 = date2[utc ? "getUTCMonth" : "getMonth"]();
  561. 5 delta = (m2 - m1) + (yearDiff * 12);
  562. 5 break;
  563. case "week":
  564. case "weeks":
  565. 32 delta = parseInt(date.difference(date1, date2, "day", utc) / 7);
  566. 32 break;
  567. case "day":
  568. case "days":
  569. 77 delta /= 24;
  570. case "hour":
  571. case "hours":
  572. 83 delta /= 60;
  573. case "minute":
  574. case "minutes":
  575. 87 delta /= 60;
  576. case "second":
  577. case "seconds":
  578. 90 delta /= 1000;
  579. case "millisecond":
  580. case "milliseconds":
  581. 94 delta *= date2.getTime() - date1.getTime();
  582. }
  583. // Round for fractional values and DST leaps
  584. 172 return round(delta); // Number (integer)
  585. },
  586. /**
  587. * Parses a date string into a date object
  588. *
  589. * @example
  590. * var aug_11_2006 = new Date(2006, 7, 11, 0);
  591. * comb.date.parse("08/11/06", "MM/dd/yy"); //aug_11_2006
  592. * comb.date.parse("11Aug2006", 'ddMMMyyyy'); //aug_11_2006
  593. * comb.date.parse("Aug2006", 'MMMyyyy'); //new Date(2006, 7, 1)
  594. * comb.date.parse("Aug 11, 2006", "MMM dd, yyyy"); //aug_11_2006
  595. * comb.date.parse("August 11, 2006", "MMMM dd, yyyy"); //aug_11_2006
  596. * comb.date.parse("Friday, August 11, 2006", "EEEE, MMMM dd, yyyy"); //aug_11_2006
  597. *
  598. * @param {String} dateStr The string to parse
  599. * @param {String} format the format of the date composed of the following options
  600. * <ul>
  601. * <li> G Era designator Text AD</li>
  602. * <li> y Year Year 1996; 96</li>
  603. * <li> M Month in year Month July; Jul; 07</li>
  604. * <li> w Week in year Number 27</li>
  605. * <li> W Week in month Number 2</li>
  606. * <li> D Day in year Number 189</li>
  607. * <li> d Day in month Number 10</li>
  608. * <li> E Day in week Text Tuesday; Tue</li>
  609. * <li> a Am/pm marker Text PM</li>
  610. * <li> H Hour in day (0-23) Number 0</li>
  611. * <li> k Hour in day (1-24) Number 24</li>
  612. * <li> K Hour in am/pm (0-11) Number 0</li>
  613. * <li> h Hour in am/pm (1-12) Number 12</li>
  614. * <li> m Minute in hour Number 30</li>
  615. * <li> s Second in minute Number 55</li>
  616. * <li> S Millisecond Number 978</li>
  617. * <li> z Time zone General time zone Pacific Standard Time; PST; GMT-08:00</li>
  618. * <li> Z Time zone RFC 822 time zone -0800 </li>
  619. * </ul>
  620. *
  621. * @returns {Date} the parsed date
  622. *
  623. *
  624. */
  625. parse:function (dateStr, format) {
  626. 28 if (!format) throw new Error('format required when calling comb.date.parse');
  627. 28 var tokens = [], regexp = buildDateEXP(format, tokens),
  628. re = new RegExp("^" + regexp + "$", "i"),
  629. match = re.exec(dateStr);
  630. 28 if (!match) {
  631. 4 return null;
  632. } // null
  633. 24 var result = [1970, 0, 1, 0, 0, 0, 0], // will get converted to a Date at the end
  634. amPm = "",
  635. valid = match.every(function (v, i) {
  636. 106 if (!i) {
  637. 24 return true;
  638. }
  639. 82 var token = tokens[i - 1];
  640. 82 var l = token.length;
  641. 82 switch (token.charAt(0)) {
  642. case 'y':
  643. 13 if (v < 100) {
  644. 3 v = parseInt(v, 10);
  645. //choose century to apply, according to a sliding window
  646. //of 80 years before and 20 years after present year
  647. 3 var year = '' + new Date().getFullYear(),
  648. century = year.substring(0, 2) * 100,
  649. cutoff = min(year.substring(2, 4) + 20, 99);
  650. 3 result[0] = (v < cutoff) ? century + v : century - 100 + v;
  651. } else {
  652. 10 result[0] = v;
  653. }
  654. 13 break;
  655. case 'M':
  656. 14 if (l > 2) {
  657. 10 var months = monthNames;
  658. 10 if (l === 3) {
  659. 4 months = monthAbbr;
  660. }
  661. //Tolerate abbreviating period in month part
  662. //Case-insensitive comparison
  663. 10 v = v.replace(".", "").toLowerCase();
  664. 10 months = months.map(function (s) {
  665. 120 return s.replace(".", "").toLowerCase();
  666. });
  667. 10 if ((v = months.indexOf(v)) == -1) {
  668. 1 return false;
  669. }
  670. } else {
  671. 4 v--;
  672. }
  673. 13 result[1] = v;
  674. 13 break;
  675. case 'E':
  676. case 'e':
  677. 6 var days = dayNames;
  678. 6 if (l == 3) {
  679. 2 days = dayAbbr;
  680. }
  681. //Case-insensitive comparison
  682. 6 v = v.toLowerCase();
  683. 6 days = days.map(function (d) {
  684. 42 return d.toLowerCase();
  685. });
  686. 6 var d = days.indexOf(v);
  687. 6 if (d == -1) {
  688. 2 v = parseInt(v);
  689. 2 if (isNaN(v) || v > days.length) {
  690. 1 return false;
  691. }
  692. } else {
  693. 4 v = d;
  694. }
  695. 5 break;
  696. case 'D':
  697. 2 result[1] = 0;
  698. case 'd':
  699. 15 result[2] = v;
  700. 15 break;
  701. case 'a': //am/pm
  702. 2 var am = "am";
  703. 2 var pm = "pm";
  704. 2 var period = /\./g;
  705. 2 v = v.replace(period, '').toLowerCase();
  706. // we might not have seen the hours field yet, so store the state and apply hour change later
  707. 2 amPm = (v == pm) ? 'p' : (v == am) ? 'a' : '';
  708. 2 break;
  709. case 'k': //hour (0-11)
  710. 1 if (v == 24) {
  711. 1 v = 0;
  712. }
  713. // fallthrough...
  714. case 'h': //hour (1-12)
  715. case 'H': //hour (0-23)
  716. case 'K': //hour (0-11)
  717. //in the 12-hour case, adjusting for am/pm requires the 'a' part
  718. //which could come before or after the hour, so we will adjust later
  719. 7 result[3] = v;
  720. 7 break;
  721. case 'm': //minutes
  722. 7 result[4] = v;
  723. 7 break;
  724. case 's': //seconds
  725. 5 result[5] = v;
  726. 5 break;
  727. case 'S': //milliseconds
  728. 4 result[6] = v;
  729. 4 break;
  730. }
  731. 80 return true;
  732. });
  733. 24 if (valid) {
  734. 22 var hours = +result[3];
  735. //account for am/pm
  736. 22 if (amPm === 'p' && hours < 12) {
  737. 1 result[3] = hours + 12; //e.g., 3pm -> 15
  738. 21 } else if (amPm === 'a' && hours == 12) {
  739. 1 result[3] = 0; //12am -> 0
  740. }
  741. 22 var dateObject = new Date(result[0], result[1], result[2], result[3], result[4], result[5], result[6]); // Date
  742. 22 var dateToken = (tokens.indexOf('d') != -1),
  743. monthToken = (tokens.indexOf('M') != -1),
  744. month = result[1],
  745. day = result[2],
  746. dateMonth = dateObject.getMonth(),
  747. dateDay = dateObject.getDate();
  748. 22 if ((monthToken && dateMonth > month) || (dateToken && dateDay > day)) {
  749. 1 return null;
  750. }
  751. 21 return dateObject; // Date
  752. } else {
  753. 2 return null;
  754. }
  755. },
  756. /**
  757. * Formats a date to the specidifed format string
  758. *
  759. * @example
  760. *
  761. * var date = new Date(2006, 7, 11, 0, 55, 12, 345);
  762. * comb.date.format(date, "EEEE, MMMM dd, yyyy"); //"Friday, August 11, 2006"
  763. * comb.date.format(date, "M/dd/yy"); //"8/11/06"
  764. * comb.date.format(date, "E"); //"6"
  765. * comb.date.format(date, "h:m a"); //"12:55 AM"
  766. * comb.date.format(date, 'h:m:s'); //"12:55:12"
  767. * comb.date.format(date, 'h:m:s.SS'); //"12:55:12.35"
  768. * comb.date.format(date, 'k:m:s.SS'); //"24:55:12.35"
  769. * comb.date.format(date, 'H:m:s.SS'); //"0:55:12.35"
  770. * comb.date.format(date, "ddMMyyyy"); //"11082006"
  771. *
  772. * @param date the date to format
  773. * @param {String} format the format of the date composed of the following options
  774. * <ul>
  775. * <li> G Era designator Text AD</li>
  776. * <li> y Year Year 1996; 96</li>
  777. * <li> M Month in year Month July; Jul; 07</li>
  778. * <li> w Week in year Number 27</li>
  779. * <li> W Week in month Number 2</li>
  780. * <li> D Day in year Number 189</li>
  781. * <li> d Day in month Number 10</li>
  782. * <li> E Day in week Text Tuesday; Tue</li>
  783. * <li> a Am/pm marker Text PM</li>
  784. * <li> H Hour in day (0-23) Number 0</li>
  785. * <li> k Hour in day (1-24) Number 24</li>
  786. * <li> K Hour in am/pm (0-11) Number 0</li>
  787. * <li> h Hour in am/pm (1-12) Number 12</li>
  788. * <li> m Minute in hour Number 30</li>
  789. * <li> s Second in minute Number 55</li>
  790. * <li> S Millisecond Number 978</li>
  791. * <li> z Time zone General time zone Pacific Standard Time; PST; GMT-08:00</li>
  792. * <li> Z Time zone RFC 822 time zone -0800 </li>
  793. * </ul>
  794. */
  795. format:function (date, format, utc) {
  796. 70 utc = utc || false;
  797. 70 var fullYear, month, day, d, hour, minute, second, millisecond;
  798. 70 if (utc) {
  799. 22 fullYear = date.getUTCFullYear(),
  800. month = date.getUTCMonth(),
  801. day = date.getUTCDay(),
  802. d = date.getUTCDate(),
  803. hour = date.getUTCHours(),
  804. minute = date.getUTCMinutes(),
  805. second = date.getUTCSeconds(),
  806. millisecond = date.getUTCMilliseconds();
  807. } else {
  808. 48 fullYear = date.getFullYear(),
  809. month = date.getMonth(),
  810. d = date.getDate(),
  811. day = date.getDay(),
  812. hour = date.getHours(),
  813. minute = date.getMinutes(),
  814. second = date.getSeconds(),
  815. millisecond = date.getMilliseconds();
  816. }
  817. 70 return format.replace(/([a-z])\1*/ig, function (match, options) {
  818. 356 var s, pad, h,
  819. c = match.charAt(0),
  820. l = match.length;
  821. 356 switch (c) {
  822. case 'd':
  823. 39 s = "" + d;
  824. case 'H':
  825. 68 !s && (s = "" + hour);
  826. case 'm':
  827. 107 !s && (s = "" + minute);
  828. case 's':
  829. 144 !s && (s = "" + second);
  830. 144 pad = true;
  831. 144 break;
  832. case 'G':
  833. 4 s = ((l < 4) ? eraAbbr : eraNames)[fullYear < 0 ? 0 : 1];
  834. 4 break;
  835. case 'y':
  836. 42 s = fullYear;
  837. 42 if (l > 1) {
  838. 42 l == 2 ? s = string.truncate("" + s, 2, true) : pad = true;
  839. }
  840. 42 break;
  841. case 'Q':
  842. case 'q':
  843. 6 s = ceil((month + 1) / 3);
  844. 6 pad = true;
  845. 6 break;
  846. case 'M':
  847. 39 if (l < 3) {
  848. 31 s = month + 1;
  849. 31 pad = true;
  850. } else {
  851. 8 s = (l == 3 ? monthAbbr : monthNames)[month];
  852. }
  853. 39 break;
  854. case 'w':
  855. 4 s = getWeekOfYear(date, 0, utc), pad = true;
  856. 4 break;
  857. case 'D':
  858. 2 s = getDayOfYear(date, utc), pad = true;
  859. 2 break;
  860. case 'E':
  861. 10 if (l < 3) {
  862. 2 s = day + 1;
  863. 2 pad = true;
  864. } else {
  865. 8 s = (l == 3 ? dayAbbr : dayNames)[day];
  866. }
  867. 10 break;
  868. case 'a':
  869. 2 s = (hour < 12) ? 'AM' : 'PM';
  870. 2 break;
  871. case 'h':
  872. 6 s = (hour % 12) || 12, pad = true;
  873. 6 break;
  874. case 'K':
  875. 2 s = (hour % 12), pad = true;
  876. 2 break;
  877. case 'k':
  878. 2 s = hour || 24, pad = true;
  879. 2 break;
  880. case 'S':
  881. 35 s = round(millisecond * pow(10, l - 3)), pad = true;
  882. 35 break;
  883. case 'v':
  884. case 'z':
  885. 31 s = comb.date.getTimezoneName(date);
  886. 31 if (s) {
  887. 27 break;
  888. }
  889. 4 l = 4;
  890. // fallthrough... use GMT if tz not available
  891. case 'Z':
  892. 4 var offset = date.getTimezoneOffset();
  893. 4 var tz = [
  894. (offset >= 0 ? "-" : "+"),
  895. string.pad(floor(abs(offset) / 60), 2, "0"),
  896. string.pad(abs(offset) % 60, 2, "0")
  897. ];
  898. 4 if (l == 4) {
  899. 4 tz.splice(0, 0, "GMT");
  900. 4 tz.splice(3, 0, ":");
  901. }
  902. 4 s = tz.join("");
  903. 4 break;
  904. // case 'Y': case 'u': case 'W': case 'F': case 'g': case 'A': case 'e':
  905. // console.log(match+" modifier unimplemented");
  906. default:
  907. 27 s = match;
  908. // throw new Error("comb.date.format: invalid pattern char: " + match);
  909. }
  910. 356 if (pad) {
  911. 272 s = string.pad(s, l, '0');
  912. }
  913. 356 return s;
  914. });
  915. }
  916. };
  917. 1date = comb.date;
  918. /**
  919. * Adds the specified year/s to the current date.
  920. *
  921. * @example
  922. *
  923. * //assuming that current year is 2012
  924. * comb.yearsFromNow(1); //2013-mm-dd hh:MM:ss
  925. *
  926. * @param {Number} val the number of years to add
  927. *
  928. * @return {Date} a date with the number of years added
  929. */
  930. 1comb.yearsFromNow = function (val) {
  931. 1 return date.add(new Date(), "years", val);
  932. };
  933. /**
  934. * Subtracts the specified year/s from the current date.
  935. *
  936. * @param {Number} val the number of years to subtract
  937. *
  938. * @return {Date} a date with the number of years subtracted
  939. */
  940. 1comb.yearsAgo = function (val) {
  941. 1 return date.add(new Date(), "years", -val);
  942. };
  943. /**
  944. * Adds the specified month/s to the current date.
  945. *
  946. * @example
  947. *
  948. * //assuming that current month is february
  949. * comb.yearsFromNow(2); //yyyy-04-dd hh:MM:ss
  950. *
  951. * @param {Number} val the number of months to add
  952. *
  953. * @return {Date} a date with the number of years added
  954. */
  955. 1comb.monthsFromNow = function (val) {
  956. 1 return date.add(new Date(), "months", val);
  957. };
  958. /**
  959. * Subtracts the specified month/s from the current date.
  960. *
  961. * @param {Number} val the number of months to subtract
  962. *
  963. * @return {Date} a date with the number of months subtracted
  964. */
  965. 1comb.monthsAgo = function (val) {
  966. 1 return date.add(new Date(), "months", -val);
  967. };
  968. /**
  969. * Adds the specified day/s to the current date.
  970. *
  971. * @param {Number} val the number of days to add
  972. *
  973. * @return {Date} a date with the number of days added
  974. */
  975. 1comb.daysFromNow = function (val) {
  976. 14 return date.add(new Date(), "days", val);
  977. };
  978. /**
  979. * Subtracts the specified day/s from the current date.
  980. *
  981. * @param {Number} val the number of days to subtract
  982. *
  983. * @return {Date} a date with the number of days subtracted
  984. */
  985. 1comb.daysAgo = function (val) {
  986. 1 return date.add(new Date(), "days", -val);
  987. };
  988. /**
  989. * Adds the specified hour/s to the current date.
  990. *
  991. * @param {Number} val the number of hours to add
  992. *
  993. * @return {Date} a date with the number of hours added
  994. */
  995. 1comb.hoursFromNow = function (val) {
  996. 1 return date.add(new Date(), "hours", val);
  997. };
  998. /**
  999. * Subtracts the specified hour/s from the current date.
  1000. *
  1001. * @param {Number} val the number of hours to subtract
  1002. *
  1003. * @return {Date} a date with the number of hours subtracted
  1004. */
  1005. 1comb.hoursAgo = function (val) {
  1006. 1 return date.add(new Date(), "hours", -val);
  1007. };
  1008. /**
  1009. * Adds the specified minute/s to the current date.
  1010. *
  1011. * @param {Number} val the number of minutes to add
  1012. *
  1013. * @return {Date} a date with the number of minutes added
  1014. */
  1015. 1comb.minutesFromNow = function (val) {
  1016. 1 return date.add(new Date(), "minutes", val);
  1017. };
  1018. /**
  1019. * Subtracts the specified minute/s from the current date.
  1020. *
  1021. * @param {Number} val the number of minutes to subtract
  1022. *
  1023. * @return {Date} a date with the number of minutes subtracted
  1024. */
  1025. 1comb.minutesAgo = function (val) {
  1026. 1 return date.add(new Date(), "minutes", -val);
  1027. };
  1028. /**
  1029. * Adds the specified second/s to the current date.
  1030. *
  1031. * @param {Number} val the number of seconds to add
  1032. *
  1033. * @return {Date} a date with the number of seconds added
  1034. */
  1035. 1comb.secondsFromNow = function (val) {
  1036. 1 return date.add(new Date(), "seconds", val);
  1037. };
  1038. /**
  1039. * Subtracts the specified second/s from the current date.
  1040. *
  1041. * @param {Number} val the number of seconds to subtract
  1042. *
  1043. * @return {Date} a date with the number of seconds subtracted
  1044. */
  1045. 1comb.secondsAgo = function (val) {
  1046. 1 return date.add(new Date(), "seconds", -val);
  1047. };
base/functions.js
Coverage100.00 SLOC157 LOC44 Missed0
  1. 1var comb = exports;
  2. /**
  3. * Determines if something is a function
  4. * @param {Anything} obj the thing to test if it is a function
  5. *
  6. * @returns {Boolean} true if the obj is a function false otherwise
  7. */
  8. 1comb.isFunction = function (obj) {
  9. 885 return typeof obj == "function";
  10. };
  11. /**
  12. * Binds a method to a particular scope
  13. *
  14. * @param {Object} scope the scope to bind the callback to
  15. * @param {String|Function} method the method to callback
  16. * @param [args] optional args to pass to the callback
  17. *
  18. * @returns {Function} the hitched function
  19. */
  20. 1comb.hitch = function (scope, method, args) {
  21. 447 var args = Array.prototype.slice.call(arguments).slice(2);
  22. 447 if (typeof method == "string") {
  23. 187 method = scope[method];
  24. }
  25. 447 if (method) {
  26. 445 return function () {
  27. 621 var scopeArgs = args.concat(Array.prototype.slice.call(arguments));
  28. 621 return method.apply(scope, scopeArgs);
  29. };
  30. } else {
  31. 2 throw new Error(method + "Method not defined");
  32. }
  33. };
  34. /**
  35. * @function
  36. * Binds a method to a particular scope
  37. *
  38. * @param {Object} scope the scope to bind the callback to
  39. * @param {String|Function} method the method to callback
  40. * @param [args] optional args to pass to the callback
  41. *
  42. * @returns {Function} the hitched function
  43. */
  44. 1comb.bind = comb.hitch;
  45. /**
  46. * Binds a method to a particular scope ignoring any new arguments passed
  47. * into the function. This is useful if you want to force particular arguments and
  48. * ignore any new ones
  49. *
  50. * @param {Object} scope the scope to bind the callback to
  51. * @param {String|Function} method the method to callback
  52. * @param [args] optional args to pass to the callback
  53. *
  54. * @returns {Function} the hitched function
  55. */
  56. 1comb.hitchIgnore = function (scope, method, args) {
  57. 16 var args = Array.prototype.slice.call(arguments).slice(2);
  58. 16 if (typeof method == "string") {
  59. 14 method = scope[method];
  60. }
  61. 16 if (method) {
  62. 14 return function () {
  63. 14 return method.apply(scope, args);
  64. };
  65. } else {
  66. 2 throw new Error(method + "Method not defined");
  67. }
  68. };
  69. /**
  70. * @function
  71. * Binds a method to a particular scope ignoring any new arguments passed
  72. * into the function. This is useful if you want to force particular arguments and
  73. * ignore any new ones
  74. *
  75. * @param {Object} scope the scope to bind the callback to
  76. * @param {String|Function} method the method to callback
  77. * @param [args] optional args to pass to the callback
  78. *
  79. * @returns {Function} the hitched function
  80. */
  81. 1comb.bindIgnore = comb.hitchIgnore;
  82. /**
  83. * Allows the passing of additional arguments to a function when it is called
  84. * especially useful for callbacks that you want to provide additional parameters to
  85. *
  86. * @param {String|Function} method the method to callback
  87. * @param {Anything} [args] variable number of arguments to pass
  88. *
  89. * @returns {Function} partially hitched function
  90. */
  91. 1comb.partial = function (method, args) {
  92. 103 var args = Array.prototype.slice.call(arguments).slice(1);
  93. 103 if (typeof method == "function") {
  94. 102 return function () {
  95. 81 var scopeArgs = args.concat(Array.prototype.slice.call(arguments));
  96. 81 return method.apply(this, scopeArgs);
  97. };
  98. } else {
  99. 1 throw new Error(method + "Method not defined");
  100. }
  101. };
  102. 1var curry = function (f, execute) {
  103. 8 return function (arg) {
  104. 8 var args = Array.prototype.slice.call(arguments);
  105. 8 return execute ? f.apply(this, arguments) : function (arg) {
  106. 6 return f.apply(this, args.concat(Array.prototype.slice.call(arguments)));
  107. };
  108. }
  109. };
  110. /**
  111. * Curries a function
  112. * @example
  113. * var curried = comb.curry(4, function(a,b,c,d){
  114. * return [a,b,c,d].join(",");
  115. * }
  116. * curried("a");
  117. * curried("b");
  118. * curried("c");
  119. * curried("d") => "a,b,c,d"
  120. *
  121. * //OR
  122. *
  123. * curried("a")("b")("c")("d") => "a,b,c,d"
  124. *
  125. *
  126. * @param {Number} depth the number of args you expect
  127. * @param {Function} cb the function to call once all args are gathered
  128. * @param {Object} [scope] what scope to call the function in
  129. *
  130. * @returns {Function} the curried version of the function
  131. * */
  132. 1comb.curry = function (depth, cb, scope) {
  133. 2 var f;
  134. 2 if (scope) {
  135. 1 f = comb.hitch(scope, cb);
  136. } else {
  137. 1 f = cb;
  138. }
  139. 2 if (depth) {
  140. 2 var len = depth - 1;
  141. 2 for (var i = len; i >= 0; i--) {
  142. 8 f = curry(f, i == len);
  143. }
  144. }
  145. 2 return f;
  146. };
base/index.js
Coverage100.00 SLOC12 LOC2 Missed0
  1. 1var objectBase = require("./object");
  2. 1objectBase.merge(exports, objectBase,
  3. require("./broadcast"),
  4. require("./functions"),
  5. require("./string"),
  6. require("./number"),
  7. require("./misc"),
  8. require("./date"),
  9. require("./array"),
  10. require("./regexp"),
  11. require("./inflections"));
base/inflections.js
Coverage100.00 SLOC215 LOC93 Missed0
  1. /*
  2. * A port of the Rails/Sequel inflections class
  3. * http://sequel.rubyforge.org/rdoc/classes/Sequel/Inflections.html
  4. */
  5. 1var array = require("./array").array, misc = require("./misc");
  6. 1var comb = exports;
  7. 1var CAMELIZE_CONVERT_REGEXP = /_(.)/g;
  8. 1var DASH = '-';
  9. 1var UNDERSCORE = '_';
  10. 1var UNDERSCORE_CONVERT_REGEXP1 = /([A-Z]+)(\d+|[A-Z][a-z])/g;
  11. 1var UNDERSCORE_CONVERT_REGEXP2 = /(\d+|[a-z])(\d+|[A-Z])/g;
  12. 1var UNDERSCORE_CONVERT_REPLACE = '$1_$2';
  13. 1var PLURALS = [], SINGULARS = [], UNCOUNTABLES = [];
  14. 1var _plural = function (rule, replacement) {
  15. 20 PLURALS.unshift([rule, replacement])
  16. };
  17. 1var _singular = function (rule, replacement) {
  18. 23 SINGULARS.unshift([rule, replacement])
  19. };
  20. /**
  21. * Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
  22. # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
  23. #
  24. # Examples:
  25. # irregular 'octopus', 'octopi'
  26. # irregular 'person', 'people'
  27. * @param singular the singular version
  28. * @param plural the plural version
  29. */
  30. 1var _irregular = function (singular, plural) {
  31. 7 _plural(new RegExp("(" + singular.substr(0, 1) + ")" + singular.substr(1) + "$"), "$1" + plural.substr(1));
  32. 7 _singular(new RegExp("(" + plural.substr(0, 1) + ")" + plural.substr(1) + "$"), "$1" + singular.substr(1));
  33. };
  34. 1var _uncountable = function (words) {
  35. 1 UNCOUNTABLES.push(misc.argsToArray(arguments))
  36. 1 UNCOUNTABLES = array.flatten(UNCOUNTABLES);
  37. };
  38. 1_plural(/$/, 's');
  39. 1_plural(/s$/i, 's');
  40. 1_plural(/(alias|(?:stat|octop|vir|b)us)$/i, '$1es');
  41. 1_plural(/(buffal|tomat)o$/i, '$1oes');
  42. 1_plural(/([ti])um$/i, '$1a');
  43. 1_plural(/sis$/i, 'ses');
  44. 1_plural(/(?:([^f])fe|([lr])f)$/i, '$1$2ves');
  45. 1_plural(/(hive)$/i, '$1s');
  46. 1_plural(/([^aeiouy]|qu)y$/i, '$1ies');
  47. 1_plural(/(x|ch|ss|sh)$/i, '$1es');
  48. 1_plural(/(matr|vert|ind)ix|ex$/i, '$1ices');
  49. 1_plural(/([m|l])ouse$/i, '$1ice');
  50. 1_plural(/^(ox)$/i, "$1en");
  51. 1_singular(/s$/i, '');
  52. 1_singular(/([ti])a$/i, '$1um');
  53. 1_singular(/(analy|ba|cri|diagno|parenthe|progno|synop|the)ses$/i, '$1sis');
  54. 1_singular(/([^f])ves$/i, '$1fe');
  55. 1_singular(/([h|t]ive)s$/i, '$1');
  56. 1_singular(/([lr])ves$/i, '$1f');
  57. 1_singular(/([^aeiouy]|qu)ies$/i, '$1y');
  58. 1_singular(/(m)ovies$/i, '$1ovie');
  59. 1_singular(/(x|ch|ss|sh)es$/i, '$1');
  60. 1_singular(/([m|l])ice$/i, '$1ouse');
  61. 1_singular(/buses$/i, 'bus');
  62. 1_singular(/oes$/i, 'o');
  63. 1_singular(/shoes$/i, 'shoe');
  64. 1_singular(/(alias|(?:stat|octop|vir|b)us)es$/i, '$1');
  65. 1_singular(/(vert|ind)ices$/i, '$1ex');
  66. 1_singular(/matrices$/i, 'matrix');
  67. 1_irregular('person', 'people');
  68. 1_irregular('man', 'men');
  69. 1_irregular('child', 'children');
  70. 1_irregular('sex', 'sexes');
  71. 1_irregular('move', 'moves');
  72. 1_irregular('quiz', 'quizzes');
  73. 1_irregular('testis', 'testes');
  74. 1_uncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "news");
  75. 1exports.singular = _singular;
  76. 1exports.plural = _plural;
  77. 1exports.uncountable = _uncountable;
  78. /**
  79. * Converts a string to camelcase
  80. *
  81. * @example
  82. * comb.camelize('hello_world') => helloWorld
  83. * comb.camelize('column_name') => columnName
  84. * comb.camelize('columnName') => columnName
  85. * comb.camelize(null) => null
  86. * comb.camelize() => undefined
  87. *
  88. * @param {String} str the string to camelize
  89. * @memberOf comb
  90. * @returns {String} the camelized version of the string
  91. */
  92. 1comb.camelize = function (str) {
  93. 8 var ret = str;
  94. 8 if (!misc.isUndefinedOrNull(str)) {
  95. 6 ret = str.replace(CAMELIZE_CONVERT_REGEXP, function (a, b) {
  96. 4 return b.toUpperCase();
  97. });
  98. }
  99. 8 return ret;
  100. };
  101. /**
  102. * The reverse of camelize. Makes an underscored form from the expression in the string.
  103. *
  104. * @example
  105. * comb.underscore('helloWorld') => hello_world
  106. * comb.underscore('column_name') => column_name
  107. * comb.underscore('columnName') => column_name
  108. * comb.underscore(null) => null
  109. * comb.underscore() => undefined
  110. * @param {String} str The string to underscore
  111. * @memberOf comb
  112. * @returns {String} the underscored version of the string
  113. * */
  114. 1comb.underscore = function (str) {
  115. 7 var ret = str;
  116. 7 if (!misc.isUndefinedOrNull(str)) {
  117. 5 ret = str.replace(UNDERSCORE_CONVERT_REGEXP1, UNDERSCORE_CONVERT_REPLACE)
  118. .replace(UNDERSCORE_CONVERT_REGEXP2, UNDERSCORE_CONVERT_REPLACE)
  119. .replace(DASH, UNDERSCORE).toLowerCase();
  120. }
  121. 7 return ret;
  122. };
  123. /**
  124. * Singularizes and camelizes the string. Also strips out all characters preceding
  125. * and including a period (".").
  126. *
  127. * @example
  128. * comb.classify('egg_and_hams') => "eggAndHam"
  129. * comb.classify('post') => "post"
  130. * comb.classify('schema.post') => "post"
  131. *
  132. * @param {String} str the string to classify
  133. * @memberOf comb
  134. * @returns {String} the classified version of the string
  135. **/
  136. 1comb.classify = function (str) {
  137. 5 var ret = str;
  138. 5 if (!misc.isUndefinedOrNull(str)) {
  139. 3 ret = comb.camelize(comb.singularize(str.replace(/.*\./g, '')));
  140. }
  141. 5 return ret;
  142. };
  143. /**
  144. * Returns the plural form of the word in the string.
  145. *
  146. * @example
  147. * comb.pluralize("post") => "posts"
  148. * comb.pluralize("octopus") => "octopi"
  149. * comb.pluralize("sheep") => "sheep"
  150. * comb.pluralize("words") => "words"
  151. * comb.pluralize("the blue mailman") => "the blue mailmen"
  152. * comb.pluralize("CamelOctopus") => "CamelOctopi"
  153. *
  154. * @param {String} str the string to pluralize
  155. * @memberOf comb
  156. * @returns {String} the pluralized version of the string
  157. **/
  158. 1comb.pluralize = function (str) {
  159. 51 var ret = str;
  160. 51 if (!misc.isUndefinedOrNull(str)) {
  161. 49 if (UNCOUNTABLES.indexOf(str) == -1) {
  162. 48 for (var i in PLURALS) {
  163. 641 var s = PLURALS[i], rule = s[0], replacement = s[1];
  164. 641 if ((ret = ret.replace(rule, replacement)) != str) {
  165. 48 break;
  166. }
  167. }
  168. }
  169. }
  170. 51 return ret;
  171. };
  172. /**
  173. * The reverse of pluralize, returns the singular form of a word in a string.
  174. *
  175. * @example
  176. * comb.singularize("posts") => "post"
  177. * comb.singularize("octopi")=> "octopus"
  178. * comb.singularize("sheep") => "sheep"
  179. * comb.singularize("word") => "word"
  180. * comb.singularize("the blue mailmen") => "the blue mailman"
  181. * comb.singularize("CamelOctopi") => "CamelOctopus"
  182. *
  183. * @param {String} str the string to singularize
  184. * @memberOf comb
  185. * @returns {String} the singularized version of the string
  186. * */
  187. 1comb.singularize = function (str) {
  188. 54 var ret = str;
  189. 54 if (!misc.isUndefinedOrNull(str)) {
  190. 52 if (UNCOUNTABLES.indexOf(str) == -1) {
  191. 51 for (var i in SINGULARS) {
  192. 785 var s = SINGULARS[i], rule = s[0], replacement = s[1];
  193. 785 if ((ret = ret.replace(rule, replacement)) != str) {
  194. 49 break;
  195. }
  196. }
  197. }
  198. }
  199. 54 return ret;
  200. };
base/misc.js
Coverage100.00 SLOC187 LOC51 Missed0
  1. 1var comb = exports,
  2. arraySlice = Array.prototype.slice;
  3. /**
  4. *
  5. * Converts an arguments object to an array
  6. *
  7. * @example
  8. *
  9. * function test(){
  10. * return comb.argsToArray(arguments);
  11. * }
  12. *
  13. * function testSlice(){
  14. * return comb.argsToArray(arguments, 3);
  15. * }
  16. *
  17. * console.log(test(1,2,3)); //[1,2,3]
  18. * console.log(test(1,2,3,4,5,6)); //[4,5,6]
  19. *
  20. * @function
  21. * @param {Arguments} args the arguments object to convert
  22. * @param {Number} [slice=0] the number of arguments to slice.
  23. * @memberOf comb
  24. * @static
  25. * @returns {Array} array version of the arguments object
  26. */
  27. 1function argsToArray(args, slice) {
  28. 1688 slice = slice || 0;
  29. 1688 return arraySlice.call(args, slice);
  30. }
  31. /**
  32. * Determines if obj is a boolean
  33. *
  34. * @param {Anything} obj the thing to test if it is a boolean
  35. *
  36. * @returns {Boolean} true if it is a boolean false otherwise
  37. * @memberOf comb
  38. * @static
  39. */
  40. 1function isBoolean(obj) {
  41. 24 var undef, type = typeof obj;
  42. 24 return obj != undef && type == "boolean" || type == "Boolean";
  43. }
  44. /**
  45. * Determines if obj is undefined
  46. *
  47. * @param {Anything} obj the thing to test if it is undefined
  48. * @returns {Boolean} true if it is undefined false otherwise
  49. * @memberOf comb
  50. * @static
  51. */
  52. 1function isUndefined(obj) {
  53. 1254 var undef;
  54. 1254 return obj !== null && obj === undef;
  55. }
  56. /**
  57. * Determins if the obj is not undefined
  58. *
  59. * @param obj the thing to test if it is not undefined
  60. *
  61. * @return {Boolean} true if it is defined false otherwise
  62. * @memberOf comb
  63. * @static
  64. */
  65. 1function isDefined(obj) {
  66. 8 return !isUndefined(obj);
  67. }
  68. /**
  69. * Determines if obj is undefined or null
  70. *
  71. * @param {Anything} obj the thing to test if it is undefined or null
  72. * @returns {Boolean} true if it is undefined or null false otherwise
  73. * @memberOf comb
  74. * @static
  75. */
  76. 1function isUndefinedOrNull(obj) {
  77. 1044 return isUndefined(obj) || isNull(obj);
  78. }
  79. /**
  80. * Determines if obj is null
  81. *
  82. * @param {Anything} obj the thing to test if it is null
  83. *
  84. * @returns {Boolean} true if it is null false otherwise
  85. * @memberOf comb
  86. * @static
  87. */
  88. 1function isNull(obj) {
  89. 1042 var undef;
  90. 1042 return obj !== undef && obj == null;
  91. }
  92. /**
  93. * Determines if obj is an Arguments object;
  94. *
  95. * @param {Anything} obj the thing to test if it is null
  96. *
  97. * @returns {Boolean} true if it is an Arguments Object false otherwise
  98. * @memberOf comb
  99. * @static
  100. */
  101. 1function isArguments(object) {
  102. 23 return !isUndefinedOrNull(object) && Object.prototype.toString.call(object) == '[object Arguments]';
  103. }
  104. 1function isInstance(obj, clazz) {
  105. 693 if (typeof clazz == "function") {
  106. 692 return obj instanceof clazz;
  107. } else {
  108. 1 return false;
  109. }
  110. }
  111. /**
  112. * Determines if obj is an instance of a particular class
  113. *
  114. * @param {Anything} obj the thing to test if it and instance of a class
  115. * @param {Object} Clazz used to determine if the object is an instance of
  116. *
  117. * @returns {Boolean} true if it is an instance of the clazz false otherwise
  118. * @memberOf comb
  119. * @static
  120. */
  121. 1function isInstanceOf(obj, clazz) {
  122. 693 return argsToArray(arguments, 1).some(function (c) {
  123. 693 return isInstance(obj, c);
  124. });
  125. }
  126. 1(function () {
  127. 1 var listeners = [];
  128. 1 var setup = false;
  129. 1 function setupListener() {
  130. 13 if (!setup) {
  131. 1 var orig = process.emit;
  132. 1 process.emit = function (event) {
  133. 680 try {
  134. 680 if (event === 'exit') {
  135. 1 listeners.forEach(function (cb) {
  136. 13 cb();
  137. });
  138. }
  139. } finally {
  140. 680 orig.apply(this, arguments);
  141. }
  142. };
  143. 1 setup = true;
  144. }
  145. }
  146. /**
  147. * Adds listeners to process.exit without having to change setMaxListeners useful if you
  148. * are writing a library and do not want to change core setting.
  149. *
  150. * @param {Funciton} cb funciton to call when process is exiting
  151. * @memberOf comb
  152. * @static
  153. */
  154. 1 function listenForExit(cb) {
  155. 13 setupListener();
  156. 13 listeners.push(cb);
  157. }
  158. 1 comb.listenForExit = listenForExit;
  159. })();
  160. 1comb.argsToArray = argsToArray;
  161. 1comb.isBoolean = isBoolean;
  162. 1comb.isUndefined = isUndefined;
  163. 1comb.isDefined = isDefined;
  164. 1comb.isUndefinedOrNull = isUndefinedOrNull;
  165. 1comb.isNull = isNull;
  166. 1comb.isArguments = isArguments;
  167. 1comb.isInstanceOf = isInstanceOf;
base/number.js
Coverage100.00 SLOC66 LOC10 Missed0
  1. 1var comb = exports;
  2. /**
  3. * Determines if obj is a number
  4. *
  5. * @param {Anything} obj the thing to test if it is a Number
  6. *
  7. * @returns {Boolean} true if it is a number false otherwise
  8. */
  9. 1comb.isNumber = function(obj) {
  10. 729 var undef;
  11. 729 return obj !== undef && obj != null && (typeof obj == "number" || obj instanceof Number);
  12. };
  13. /**
  14. * @private
  15. */
  16. 1var round = Math.round, pow = Math.pow;
  17. /**
  18. * @namespace Utilities for numbers
  19. */
  20. 1comb.number = {
  21. /**@lends comb.number*/
  22. /**
  23. * Rounds a number to the specified places.
  24. *
  25. * @example
  26. *
  27. * comb.number.round(10.000009, 2); //10
  28. * comb.number.round(10.000009, 5); //10.00001
  29. * comb.number.round(10.0009, 3); //10.001
  30. * comb.number.round(10.0009, 2); //10
  31. * comb.number.round(10.0009, 3); //10.001
  32. *
  33. * @param {Number} num the number to round.
  34. * @param {Number} places the number of places to round to.
  35. */
  36. round : function(number, places, increment) {
  37. 6 increment = increment || 1e-20;
  38. 6 var factor = 10 / (10 * (increment || 10));
  39. 6 return (Math.ceil(factor * +number) / factor).toFixed(places) * 1; // Number
  40. },
  41. /**
  42. * Rounds a number to the specified places, rounding up.
  43. *
  44. * @example
  45. *
  46. * comb.number.roundCeil(10.000001, 2); //10.01
  47. * comb.number.roundCeil(10.000002, 5); //10.00001
  48. * comb.number.roundCeil(10.0003, 3); //10.001
  49. * comb.number.roundCeil(10.0004, 2); //10.01
  50. * comb.number.roundCeil(10.0005, 3); //10.001
  51. * comb.number.roundCeil(10.0002, 2); //10.01
  52. *
  53. * @param {Number} num the number to round.
  54. * @param {Number} places the number of places to round to.
  55. */
  56. roundCeil : function(number, places){
  57. 6 return Math.ceil(number * Math.pow(10, places))/Math.pow(10, places);
  58. }
  59. };
base/object.js
Coverage100.00 SLOC300 LOC87 Missed0
  1. 1var comb = exports,
  2. misc = require("./misc.js"),
  3. isUndefinedOrNull = misc.isUndefined,
  4. isArguments = misc.isArguments,
  5. pSlice = Array.prototype.slice;
  6. //taken from node js assert.js
  7. //https://github.com/joyent/node/blob/master/lib/assert.js
  8. 1var _deepEqual = function (actual, expected) {
  9. // 7.1. All identical values are equivalent, as determined by ===.
  10. 43 if (actual === expected) {
  11. 3 return true;
  12. 40 } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
  13. 1 if (actual.length != expected.length) return false;
  14. 1 for (var i = 0; i < actual.length; i++) {
  15. 3 if (actual[i] !== expected[i]) return false;
  16. }
  17. 1 return true;
  18. // 7.2. If the expected value is a Date object, the actual value is
  19. // equivalent if it is also a Date object that refers to the same time.
  20. 39 } else if (actual instanceof Date && expected instanceof Date) {
  21. 2 return actual.getTime() === expected.getTime();
  22. // 7.3 If the expected value is a RegExp object, the actual value is
  23. // equivalent if it is also a RegExp object with the same source and
  24. // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
  25. 37 } else if (actual instanceof RegExp && expected instanceof RegExp) {
  26. 2 return actual.source === expected.source &&
  27. actual.global === expected.global &&
  28. actual.multiline === expected.multiline &&
  29. actual.lastIndex === expected.lastIndex &&
  30. actual.ignoreCase === expected.ignoreCase;
  31. // 7.4. Other pairs that do not both pass typeof value == 'object',
  32. // equivalence is determined by ==.
  33. 35 } else if (typeof actual != 'object' && typeof expected != 'object') {
  34. 13 return actual == expected;
  35. // 7.5 For all other Object pairs, including Array objects, equivalence is
  36. // determined by having the same number of owned properties (as verified
  37. // with Object.prototype.hasOwnProperty.call), the same set of keys
  38. // (although not necessarily the same order), equivalent values for every
  39. // corresponding key, and an identical 'prototype' property. Note: this
  40. // accounts for both named and indexed properties on Arrays.
  41. } else {
  42. 22 return objEquiv(actual, expected);
  43. }
  44. };
  45. 1var objEquiv = function (a, b) {
  46. 22 if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
  47. 7 return false;
  48. // an identical 'prototype' property.
  49. 15 if (a.prototype !== b.prototype) return false;
  50. //~~~I've managed to break Object.keys through screwy arguments passing.
  51. // Converting to array solves the problem.
  52. 15 if (isArguments(a)) {
  53. 2 if (!isArguments(b)) {
  54. 1 return false;
  55. }
  56. 1 a = pSlice.call(a);
  57. 1 b = pSlice.call(b);
  58. 1 return _deepEqual(a, b);
  59. }
  60. 13 try {
  61. 13 var ka = Object.keys(a),
  62. kb = Object.keys(b),
  63. key, i;
  64. } catch (e) {//happens when one is a string literal and the other isn't
  65. 1 return false;
  66. }
  67. // having the same number of owned properties (keys incorporates
  68. // hasOwnProperty)
  69. 12 if (ka.length != kb.length)
  70. 2 return false;
  71. //the same set of keys (although not necessarily the same order),
  72. 10 ka.sort();
  73. 10 kb.sort();
  74. //~~~cheap key test
  75. 10 for (i = ka.length - 1; i >= 0; i--) {
  76. 10 if (ka[i] != kb[i])
  77. 2 return false;
  78. }
  79. //equivalent values for every corresponding key, and
  80. //~~~possibly expensive deep test
  81. 8 for (i = ka.length - 1; i >= 0; i--) {
  82. 8 key = ka[i];
  83. 11 if (!_deepEqual(a[key], b[key])) return false;
  84. }
  85. 5 return true;
  86. }
  87. 1var merge = function (target, source) {
  88. 49 var name, s;
  89. 49 for (name in source) {
  90. 207 s = source[name];
  91. 207 if (!(name in target) || (target[name] !== s)) {
  92. 206 target[name] = s;
  93. }
  94. }
  95. 49 return target;
  96. };
  97. 1var deepMerge = function (target, source) {
  98. 16 var name, s, t;
  99. 16 for (name in source) {
  100. 22 s = source[name], t = target[name];
  101. 22 if (!_deepEqual(t, s)) {
  102. 22 if (comb.isHash(t) && comb.isHash(s)) {
  103. 4 target[name] = deepMerge(t, s);
  104. 18 } else if (comb.isHash(s)) {
  105. 5 target[name] = deepMerge({}, s);
  106. } else {
  107. 13 target[name] = s;
  108. }
  109. }
  110. }
  111. 16 return target;
  112. };
  113. /**
  114. * Merges objects together
  115. * NOTE: this function takes a variable number of objects to merge
  116. *
  117. * @example
  118. *
  119. * var myObj = {};
  120. * comb.merge(myObj, {test : true});
  121. *
  122. * myObj.test => true
  123. *
  124. * comb.merge(myObj, {test : false}, {test2 : false}, {test3 : "hello", test4 : "world"});
  125. * myObj.test => false
  126. * myObj.test2 => false
  127. * myObj.test3 => "hello"
  128. * myObj.test4 => "world"
  129. *
  130. *
  131. * @param {Object} obj the object to merge into
  132. * @param {Object} props variable number of objects to merge into the obj
  133. *
  134. * @returns {Object} the merged object
  135. */
  136. 1comb.merge = function (obj, props) {
  137. 31 if (!obj) {
  138. 1 obj = {};
  139. }
  140. 31 for (var i = 1, l = arguments.length; i < l; i++) {
  141. 49 merge(obj, arguments[i]);
  142. }
  143. 31 return obj; // Object
  144. };
  145. /**
  146. * Merges objects together only overriding properties that are different.
  147. * NOTE: this function takes a variable number of objects to merge
  148. *
  149. * @example
  150. *
  151. * var myObj = {my : {cool : {property1 : 1, property2 : 2}}};
  152. * comb.deepMerge(myObj, {my : {cool : {property3 : 3}}});
  153. *
  154. * myObj.my.cool.property1 => 1
  155. * myObj.my.cool.property2 => 2
  156. * myObj.my.cool.property3 => 3
  157. *
  158. *
  159. * @param {Object} obj the object to merge into
  160. * @param {Object} props variable number of objects to merge into the obj
  161. *
  162. * @returns {Object} the merged object
  163. */
  164. 1comb.deepMerge = function (obj, props) {
  165. 4 if (!obj) {
  166. 1 obj = {};
  167. }
  168. 4 for (var i = 1, l = arguments.length; i < l; i++) {
  169. 7 deepMerge(obj, arguments[i]);
  170. }
  171. 4 return obj; // Object
  172. };
  173. /**
  174. * Extends the prototype of an object if it exists otherwise it extends the object.
  175. *
  176. * @example
  177. *
  178. * var MyObj = function(){};
  179. * MyObj.prototype.test = true;
  180. * comb.extend(MyObj, {test2 : false, test3 : "hello", test4 : "world"});
  181. *
  182. * var myObj = new MyObj();
  183. *
  184. * myObj.test => true
  185. * myObj.test2 => false
  186. * myObj.test3 => "hello"
  187. * myObj.test4 => "world"
  188. *
  189. * var myObj2 = {};
  190. * myObj2.test = true;
  191. * comb.extend(myObj2, {test2 : false, test3 : "hello", test4 : "world"});
  192. *
  193. * myObj2.test => true
  194. * myObj2.test2 => false
  195. * myObj2.test3 => "hello"
  196. * myObj2.test4 => "world"
  197. *
  198. *
  199. * @param {Object} parent the parent object to extend
  200. * @param {Object} extend the extension object to mixin to the parent
  201. *
  202. * @returns {Object} returns the extended object
  203. */
  204. 1comb.extend = function (parent, extend) {
  205. 2 var proto = parent.prototype || parent;
  206. 2 return exports.merge(proto, extend);
  207. };
  208. /**
  209. * Determines if obj is an object
  210. *
  211. * @param {Anything} obj the thing to test if it is an object
  212. *
  213. * @returns {Boolean} true if it is an object false otherwise
  214. */
  215. 1comb.isObject = function (obj) {
  216. 87 var undef;
  217. 87 return obj != null && obj != undef && typeof obj == "object";
  218. };
  219. /**
  220. * Determines if an object is just a hash and not a qualified Object such as Number
  221. *
  222. * @example
  223. * comb.isHash({}) => true
  224. * comb.isHash({1 : 2, a : "b"}) => true
  225. * comb.isHash(new Date()) => false
  226. * comb.isHash(new String()) => false
  227. * comb.isHash(new Number()) => false
  228. * comb.isHash(new Boolean()) => false
  229. * comb.isHash() => false
  230. * comb.isHash("") => false
  231. * comb.isHash(1) => false
  232. * comb.isHash(false) => false
  233. * comb.isHash(true) => false
  234. * @param {Anything} obj the thing to test if it is a hash
  235. *
  236. * @returns {Boolean} true if it is a hash false otherwise
  237. */
  238. 1comb.isHash = function (obj) {
  239. 72 var ret = comb.isObject(obj);
  240. 72 return ret && obj.constructor === Object;
  241. }
  242. /**
  243. * Determines if an object is empty
  244. *
  245. * @example
  246. *
  247. * comb.isEmpty({}) => true
  248. * comb.isEmpty({a : 1}) => false
  249. *
  250. * @param object the object to test
  251. * @returns {Boolean} true if the object is empty;
  252. */
  253. 1comb.isEmpty = function (object) {
  254. 5 if (comb.isObject(object)) {
  255. 4 for (var i in object) {
  256. 2 if (object.hasOwnProperty(i)) {
  257. 2 return false;
  258. }
  259. }
  260. }
  261. 3 return true;
  262. };
  263. /**
  264. * Determines if two things are deep equal.
  265. *
  266. * @example
  267. *
  268. * comb.deepEqual({a : 1, b : 2}, {a : 1, b : 2}) => true
  269. * comb.deepEqual({a : 1}, {a : 1, b : 2}) => false
  270. *
  271. * @param o1 the first thing to compare
  272. * @param o3 the second thing to compare
  273. * @return {Boolean}
  274. */
  275. 1comb.deepEqual = function (o1, o2) {
  276. 12 return _deepEqual(o1, o2);
  277. }
base/regexp.js
Coverage100.00 SLOC48 LOC11 Missed0
  1. 1var comb = exports;
  2. /**
  3. * Tests if something is a regular expression.
  4. *
  5. * @example
  6. *
  7. * comb.isRegExp(/hello/); //true
  8. * comb.isRegExp("hello"); //false
  9. *
  10. * @param obj the thing to test.
  11. * @return {Boolean}
  12. * @static
  13. * @memberOf comb
  14. *
  15. */
  16. 1function isRegExp(obj) {
  17. 7 var undef;
  18. 7 return obj !== undef && obj != null && (obj instanceof RegExp);
  19. }
  20. 1comb.isRexExp = isRegExp;
  21. 1comb.isRegExp = isRegExp;
  22. /**
  23. * @namespace Regeular expression utilities
  24. *
  25. */
  26. 1comb.regexp = {
  27. /**@lends comb.regexp*/
  28. /**
  29. * Escapes a string
  30. *
  31. * @param {String} str the string to escape
  32. * @param {String} [except] characters to ignore
  33. *
  34. * @returns {String} the escaped string
  35. */
  36. escapeString:function (str, except) {
  37. 31 return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function (ch) {
  38. 31 if (except && except.indexOf(ch) != -1) {
  39. 15 return ch;
  40. }
  41. 16 return "\\" + ch;
  42. }); // String
  43. }
  44. };
base/string.js
Coverage100.00 SLOC1184 LOC131 Missed0
  1. 1var comb = exports, date, misc = require("./misc");
  2. /**
  3. * Tests if something is a string.
  4. *
  5. * @example
  6. *
  7. * comb.isString("true") //true
  8. * comb.isString(true) //false
  9. *
  10. * @param obj the thing to test
  11. * @return {Boolean} returns true if the argument is a string.
  12. * @static
  13. * @memberOf comb
  14. */
  15. 1function isString(obj) {
  16. 924 var undef;
  17. 924 return obj != undef && (typeof obj == "string" || obj instanceof String);
  18. }
  19. 1comb.isString = isString;
  20. 1var FORMAT_REGEX = /%((?:-?\+?.?\d*)?|(?:\[[^\[|\]]*\]))?([sjdDZ])/g;
  21. 1var INTERP_REGEX = /{(?:\[([^\[|\]]*)\])?(\w+)}/g;
  22. 1var STR_FORMAT = /(-?)(\+?)([A-Z|a-z|\W]?)([1-9][0-9]*)?$/;
  23. 1var OBJECT_FORMAT = /([1-9][0-9]*)$/g;
  24. 1var formatString = function (string, format) {
  25. 58 var match = format.match(STR_FORMAT), ret = string, cString = comb.string;
  26. 58 if (match) {
  27. 58 var isLeftJustified = match[1], padChar = match[3], width = match[4];
  28. 58 if (width) {
  29. 58 width = parseInt(width);
  30. 58 if (ret.length < width) {
  31. 30 ret = cString.pad(ret, width, padChar, isLeftJustified);
  32. } else {
  33. 28 ret = cString.truncate(ret, width);
  34. }
  35. }
  36. }
  37. 58 return ret;
  38. };
  39. 1var formatNumber = function (number, format) {
  40. 8 if (typeof number == "number") {
  41. 7 var cString = comb.string, ret = "" + number;
  42. 7 var match = format.match(STR_FORMAT);
  43. 7 if (match) {
  44. 7 var isLeftJustified = match[1], signed = match[2], padChar = match[3], width = match[4];
  45. 7 if (signed) {
  46. 3 ret = (number > 0 ? "+" : "") + ret;
  47. }
  48. 7 if (width) {
  49. 5 width = parseInt(width);
  50. 5 if (ret.length < width) {
  51. 4 ret = cString.pad(ret, width, padChar || "0", isLeftJustified);
  52. } else {
  53. 1 ret = cString.truncate(ret, width);
  54. }
  55. }
  56. }
  57. } else {
  58. 1 throw new Error("comb.string.format : when using %d the parameter must be a number!");
  59. }
  60. 7 return ret;
  61. };
  62. 1var formatObject = function (object, format) {
  63. 4 var ret, match = format.match(OBJECT_FORMAT), spacing = 0;
  64. 4 if (match) {
  65. 4 spacing = parseInt(match[0]);
  66. 4 if (isNaN(spacing)) spacing = 0;
  67. }
  68. 4 try {
  69. 4 ret = JSON.stringify(object, null, spacing);
  70. } catch (e) {
  71. 1 throw new Error("comb.string.format : Unable to parse json from ", object);
  72. }
  73. 3 return ret;
  74. };
  75. 1var styles = {
  76. //styles
  77. bold:1,
  78. bright:1,
  79. italic:3,
  80. underline:4,
  81. blink:5,
  82. inverse:7,
  83. crossedOut:9,
  84. red:31,
  85. green:32,
  86. yellow:33,
  87. blue:34,
  88. magenta:35,
  89. cyan:36,
  90. white:37,
  91. redBackground:41,
  92. greenBackground:42,
  93. yellowBackground:43,
  94. blueBackground:44,
  95. magentaBackground:45,
  96. cyanBackground:46,
  97. whiteBackground:47,
  98. encircled:52,
  99. overlined:53,
  100. grey:90,
  101. black:90
  102. };
  103. /**@namespace comb characters*/
  104. 1comb.characters = {
  105. /**@lends comb.characters*/
  106. /**
  107. * ☺
  108. */
  109. SMILEY:"☺",
  110. /**
  111. * ☻
  112. */
  113. SOLID_SMILEY:"☻",
  114. /**
  115. * ♥
  116. */
  117. HEART:"♥",
  118. /**
  119. * ♦
  120. */
  121. DIAMOND:"♦",
  122. /**
  123. * ♣
  124. */
  125. CLOVE:"♣",
  126. /**
  127. * â™ 
  128. */
  129. SPADE:"â™ ",
  130. /**
  131. * •
  132. */
  133. DOT:"•",
  134. /**
  135. * â—˜
  136. */
  137. SQUARE_CIRCLE:"â—˜",
  138. /**
  139. * â—‹
  140. */
  141. CIRCLE:"â—‹",
  142. /**
  143. * â—™
  144. */
  145. FILLED_SQUARE_CIRCLE:"â—™",
  146. /**
  147. * ♂
  148. */
  149. MALE:"♂",
  150. /**
  151. * ♀
  152. */
  153. FEMALE:"♀",
  154. /**
  155. * ♪
  156. */
  157. EIGHT_NOTE:"♪",
  158. /**
  159. * ♫
  160. */
  161. DOUBLE_EIGHT_NOTE:"♫",
  162. /**
  163. * ☼
  164. */
  165. SUN:"☼",
  166. /**
  167. * â–º
  168. */
  169. PLAY:"â–º",
  170. /**
  171. * â—„
  172. */
  173. REWIND:"â—„",
  174. /**
  175. * ↕
  176. */
  177. UP_DOWN:"↕",
  178. /**
  179. * ¶
  180. */
  181. PILCROW:"¶",
  182. /**
  183. * §
  184. */
  185. SECTION:"§",
  186. /**
  187. * â–¬
  188. */
  189. THICK_MINUS:"â–¬",
  190. /**
  191. * ↨
  192. */
  193. SMALL_UP_DOWN:"↨",
  194. /**
  195. * ↑
  196. */
  197. UP_ARROW:"↑",
  198. /**
  199. * ↓
  200. */
  201. DOWN_ARROW:"↓",
  202. /**
  203. * →
  204. */
  205. RIGHT_ARROW:"→",
  206. /**
  207. * ←
  208. */
  209. LEFT_ARROW:"←",
  210. /**
  211. * ∟
  212. */
  213. RIGHT_ANGLE:"∟",
  214. /**
  215. * ↔
  216. */
  217. LEFT_RIGHT_ARROW:"↔",
  218. /**
  219. * â–²
  220. */
  221. TRIANGLE:"â–²",
  222. /**
  223. * â–¼
  224. */
  225. DOWN_TRIANGLE:"â–¼",
  226. /**
  227. * ⌂
  228. */
  229. HOUSE:"⌂",
  230. /**
  231. * Ç
  232. */
  233. C_CEDILLA:"Ç",
  234. /**
  235. * ü
  236. */
  237. U_UMLAUT:"ü",
  238. /**
  239. * é
  240. */
  241. E_ACCENT:"é",
  242. /**
  243. * â
  244. */
  245. A_LOWER_CIRCUMFLEX:"â",
  246. /**
  247. * ä
  248. */
  249. A_LOWER_UMLAUT:"ä",
  250. /**
  251. * à
  252. */
  253. A_LOWER_GRAVE_ACCENT:"à",
  254. /**
  255. * å
  256. */
  257. A_LOWER_CIRCLE_OVER:"Ã¥",
  258. /**
  259. * ç
  260. */
  261. C_LOWER_CIRCUMFLEX:"ç",
  262. /**
  263. * ê
  264. */
  265. E_LOWER_CIRCUMFLEX:"ê",
  266. /**
  267. * ë
  268. */
  269. E_LOWER_UMLAUT:"ë",
  270. /**
  271. * è
  272. */
  273. E_LOWER_GRAVE_ACCENT:"è",
  274. /**
  275. * ï
  276. */
  277. I_LOWER_UMLAUT:"ï",
  278. /**
  279. * î
  280. */
  281. I_LOWER_CIRCUMFLEX:"î",
  282. /**
  283. * ì
  284. */
  285. I_LOWER_GRAVE_ACCENT:"ì",
  286. /**
  287. * Ä
  288. */
  289. A_UPPER_UMLAUT:"Ä",
  290. /**
  291. * Ã…
  292. */
  293. A_UPPER_CIRCLE:"Ã…",
  294. /**
  295. * É
  296. */
  297. E_UPPER_ACCENT:"É",
  298. /**
  299. * æ
  300. */
  301. A_E_LOWER:"æ",
  302. /**
  303. * Æ
  304. */
  305. A_E_UPPER:"Æ",
  306. /**
  307. * ô
  308. */
  309. O_LOWER_CIRCUMFLEX:"ô",
  310. /**
  311. * ö
  312. */
  313. O_LOWER_UMLAUT:"ö",
  314. /**
  315. * ò
  316. */
  317. O_LOWER_GRAVE_ACCENT:"ò",
  318. /**
  319. * û
  320. */
  321. U_LOWER_CIRCUMFLEX:"û",
  322. /**
  323. * ù
  324. */
  325. U_LOWER_GRAVE_ACCENT:"ù",
  326. /**
  327. * ÿ
  328. */
  329. Y_LOWER_UMLAUT:"ÿ",
  330. /**
  331. * Ö
  332. */
  333. O_UPPER_UMLAUT:"Ö",
  334. /**
  335. * Ü
  336. */
  337. U_UPPER_UMLAUT:"Ü",
  338. /**
  339. * ¢
  340. */
  341. CENTS:"¢",
  342. /**
  343. * £
  344. */
  345. POUND:"£",
  346. /**
  347. * ¥
  348. */
  349. YEN:"Â¥",
  350. /**
  351. * ¤
  352. */
  353. CURRENCY:"¤",
  354. /**
  355. * â‚§
  356. */
  357. PTS:"â‚§",
  358. /**
  359. * Æ’
  360. */
  361. FUNCTION:"Æ’",
  362. /**
  363. * á
  364. */
  365. A_LOWER_ACCENT:"á",
  366. /**
  367. * í
  368. */
  369. I_LOWER_ACCENT:"í",
  370. /**
  371. * ó
  372. */
  373. O_LOWER_ACCENT:"ó",
  374. /**
  375. * ú
  376. */
  377. U_LOWER_ACCENT:"ú",
  378. /**
  379. * ñ
  380. */
  381. N_LOWER_TILDE:"ñ",
  382. /**
  383. * Ñ
  384. */
  385. N_UPPER_TILDE:"Ñ",
  386. /**
  387. * ª
  388. */
  389. A_SUPER:"ª",
  390. /**
  391. * º
  392. */
  393. O_SUPER:"º",
  394. /**
  395. * ¿
  396. */
  397. UPSIDEDOWN_QUESTION:"¿",
  398. /**
  399. * ⌐
  400. */
  401. SIDEWAYS_L:"⌐",
  402. /**
  403. * ¬
  404. */
  405. NEGATION:"¬",
  406. /**
  407. * ½
  408. */
  409. ONE_HALF:"½",
  410. /**
  411. * ¼
  412. */
  413. ONE_FOURTH:"¼",
  414. /**
  415. * ¡
  416. */
  417. UPSIDEDOWN_EXCLAMATION:"¡",
  418. /**
  419. * «
  420. */
  421. DOUBLE_LEFT:"«",
  422. /**
  423. * »
  424. */
  425. DOUBLE_RIGHT:"»",
  426. /**
  427. * â–‘
  428. */
  429. LIGHT_SHADED_BOX:"â–‘",
  430. /**
  431. * â–’
  432. */
  433. MEDIUM_SHADED_BOX:"â–’",
  434. /**
  435. * â–“
  436. */
  437. DARK_SHADED_BOX:"â–“",
  438. /**
  439. * │
  440. */
  441. VERTICAL_LINE:"│",
  442. /**
  443. * ┤
  444. */
  445. MAZE__SINGLE_RIGHT_T:"┤",
  446. /**
  447. * ┐
  448. */
  449. MAZE_SINGLE_RIGHT_TOP:"┐",
  450. /**
  451. * ┘
  452. */
  453. MAZE_SINGLE_RIGHT_BOTTOM_SMALL:"┘",
  454. /**
  455. * ┌
  456. */
  457. MAZE_SINGLE_LEFT_TOP_SMALL:"┌",
  458. /**
  459. * â””
  460. */
  461. MAZE_SINGLE_LEFT_BOTTOM_SMALL:"â””",
  462. /**
  463. * ├
  464. */
  465. MAZE_SINGLE_LEFT_T:"├",
  466. /**
  467. * â”´
  468. */
  469. MAZE_SINGLE_BOTTOM_T:"â”´",
  470. /**
  471. * ┬
  472. */
  473. MAZE_SINGLE_TOP_T:"┬",
  474. /**
  475. * ┼
  476. */
  477. MAZE_SINGLE_CENTER:"┼",
  478. /**
  479. * ─
  480. */
  481. MAZE_SINGLE_HORIZONTAL_LINE:"─",
  482. /**
  483. * â•¡
  484. */
  485. MAZE_SINGLE_RIGHT_DOUBLECENTER_T:"â•¡",
  486. /**
  487. * â•›
  488. */
  489. MAZE_SINGLE_RIGHT_DOUBLE_BL:"â•›",
  490. /**
  491. * â•¢
  492. */
  493. MAZE_SINGLE_RIGHT_DOUBLE_T:"â•¢",
  494. /**
  495. * â•–
  496. */
  497. MAZE_SINGLE_RIGHT_DOUBLEBOTTOM_TOP:"â•–",
  498. /**
  499. * â••
  500. */
  501. MAZE_SINGLE_RIGHT_DOUBLELEFT_TOP:"â••",
  502. /**
  503. * ╞
  504. */
  505. MAZE_SINGLE_LEFT_DOUBLE_T:"╞",
  506. /**
  507. * â•§
  508. */
  509. MAZE_SINGLE_BOTTOM_DOUBLE_T:"â•§",
  510. /**
  511. * ╤
  512. */
  513. MAZE_SINGLE_TOP_DOUBLE_T:"╤",
  514. /**
  515. * â•¥
  516. */
  517. MAZE_SINGLE_TOP_DOUBLECENTER_T:"â•¥",
  518. /**
  519. * ╨
  520. */
  521. MAZE_SINGLE_BOTTOM_DOUBLECENTER_T:"╨",
  522. /**
  523. * ╘
  524. */
  525. MAZE_SINGLE_LEFT_DOUBLERIGHT_BOTTOM:"╘",
  526. /**
  527. * â•’
  528. */
  529. MAZE_SINGLE_LEFT_DOUBLERIGHT_TOP:"â•’",
  530. /**
  531. * â•“
  532. */
  533. MAZE_SINGLE_LEFT_DOUBLEBOTTOM_TOP:"â•“",
  534. /**
  535. * â•™
  536. */
  537. MAZE_SINGLE_LEFT_DOUBLETOP_BOTTOM:"â•™",
  538. /**
  539. * Γ
  540. */
  541. MAZE_SINGLE_LEFT_TOP:"Γ",
  542. /**
  543. * ╜
  544. */
  545. MAZE_SINGLE_RIGHT_BOTTOM:"╜",
  546. /**
  547. * ╟
  548. */
  549. MAZE_SINGLE_LEFT_CENTER:"╟",
  550. /**
  551. * â•«
  552. */
  553. MAZE_SINGLE_DOUBLECENTER_CENTER:"â•«",
  554. /**
  555. * ╪
  556. */
  557. MAZE_SINGLE_DOUBLECROSS_CENTER:"╪",
  558. /**
  559. * â•£
  560. */
  561. MAZE_DOUBLE_LEFT_CENTER:"â•£",
  562. /**
  563. * â•‘
  564. */
  565. MAZE_DOUBLE_VERTICAL:"â•‘",
  566. /**
  567. * â•—
  568. */
  569. MAZE_DOUBLE_RIGHT_TOP:"â•—",
  570. /**
  571. * ╝
  572. */
  573. MAZE_DOUBLE_RIGHT_BOTTOM:"╝",
  574. /**
  575. * ╚
  576. */
  577. MAZE_DOUBLE_LEFT_BOTTOM:"╚",
  578. /**
  579. * â•”
  580. */
  581. MAZE_DOUBLE_LEFT_TOP:"â•”",
  582. /**
  583. * â•©
  584. */
  585. MAZE_DOUBLE_BOTTOM_T:"â•©",
  586. /**
  587. * ╦
  588. */
  589. MAZE_DOUBLE_TOP_T:"╦",
  590. /**
  591. * â• 
  592. */
  593. MAZE_DOUBLE_LEFT_T:"â• ",
  594. /**
  595. * ═
  596. */
  597. MAZE_DOUBLE_HORIZONTAL:"═",
  598. /**
  599. * ╬
  600. */
  601. MAZE_DOUBLE_CROSS:"╬",
  602. /**
  603. * â–ˆ
  604. */
  605. SOLID_RECTANGLE:"â–ˆ",
  606. /**
  607. * ▌
  608. */
  609. THICK_LEFT_VERTICAL:"▌",
  610. /**
  611. * ▐
  612. */
  613. THICK_RIGHT_VERTICAL:"▐",
  614. /**
  615. * â–„
  616. */
  617. SOLID_SMALL_RECTANGLE_BOTTOM:"â–„",
  618. /**
  619. * â–€
  620. */
  621. SOLID_SMALL_RECTANGLE_TOP:"â–€",
  622. /**
  623. * Φ
  624. */
  625. PHI_UPPER:"Φ",
  626. /**
  627. * ∞
  628. */
  629. INFINITY:"∞",
  630. /**
  631. * ∩
  632. */
  633. INTERSECTION:"∩",
  634. /**
  635. * ≡
  636. */
  637. DEFINITION:"≡",
  638. /**
  639. * ±
  640. */
  641. PLUS_MINUS:"±",
  642. /**
  643. * ≥
  644. */
  645. GT_EQ:"≥",
  646. /**
  647. * ≤
  648. */
  649. LT_EQ:"≤",
  650. /**
  651. * ⌠
  652. */
  653. THEREFORE:"⌠",
  654. /**
  655. * ∵
  656. */
  657. SINCE:"∵",
  658. /**
  659. * ∄
  660. */
  661. DOESNOT_EXIST:"∄",
  662. /**
  663. * ∃
  664. */
  665. EXISTS:"∃",
  666. /**
  667. * ∀
  668. */
  669. FOR_ALL:"∀",
  670. /**
  671. * ⊕
  672. */
  673. EXCLUSIVE_OR:"⊕",
  674. /**
  675. * ⌡
  676. */
  677. BECAUSE:"⌡",
  678. /**
  679. * ÷
  680. */
  681. DIVIDE:"÷",
  682. /**
  683. * ≈
  684. */
  685. APPROX:"≈",
  686. /**
  687. * °
  688. */
  689. DEGREE:"°",
  690. /**
  691. * ∙
  692. */
  693. BOLD_DOT:"∙",
  694. /**
  695. * ·
  696. */
  697. DOT_SMALL:"·",
  698. /**
  699. * √
  700. */
  701. CHECK:"√",
  702. /**
  703. * ✗
  704. */
  705. ITALIC_X:"✗",
  706. /**
  707. * ⁿ
  708. */
  709. SUPER_N:"ⁿ",
  710. /**
  711. * ²
  712. */
  713. SQUARED:"²",
  714. /**
  715. * ³
  716. */
  717. CUBED:"³",
  718. /**
  719. * â– 
  720. */
  721. SOLID_BOX:"â– ",
  722. /**
  723. * ‰
  724. */
  725. PERMILE:"‰",
  726. /**
  727. * ®
  728. */
  729. REGISTERED_TM:"®",
  730. /**
  731. * ©
  732. */
  733. COPYRIGHT:"©",
  734. /**
  735. * â„¢
  736. */
  737. TRADEMARK:"â„¢",
  738. /**
  739. * β
  740. */
  741. BETA:"β",
  742. /**
  743. * γ
  744. */
  745. GAMMA:"γ",
  746. /**
  747. * ζ
  748. */
  749. ZETA:"ζ",
  750. /**
  751. * η
  752. */
  753. ETA:"η",
  754. /**
  755. * ι
  756. */
  757. IOTA:"ι",
  758. /**
  759. * κ
  760. */
  761. KAPPA:"κ",
  762. /**
  763. * λ
  764. */
  765. LAMBDA:"λ",
  766. /**
  767. * ν
  768. */
  769. NU:"ν",
  770. /**
  771. * ξ
  772. */
  773. XI:"ξ",
  774. /**
  775. * ο
  776. */
  777. OMICRON:"ο",
  778. /**
  779. * ρ
  780. */
  781. RHO:"ρ",
  782. /**
  783. * Ï…
  784. */
  785. UPSILON:"Ï…",
  786. /**
  787. * φ
  788. */
  789. CHI_LOWER:"φ",
  790. /**
  791. * χ
  792. */
  793. CHI_UPPER:"χ",
  794. /**
  795. * ψ
  796. */
  797. PSI:"ψ",
  798. /**
  799. * α
  800. */
  801. ALPHA:"α",
  802. /**
  803. * ß
  804. */
  805. ESZETT:"ß",
  806. /**
  807. * π
  808. */
  809. PI:"Ï€",
  810. /**
  811. * Σ
  812. */
  813. SIGMA_UPPER:"Σ",
  814. /**
  815. * σ
  816. */
  817. SIGMA_LOWER:"σ",
  818. /**
  819. * µ
  820. */
  821. MU:"µ",
  822. /**
  823. * Ï„
  824. */
  825. TAU:"Ï„",
  826. /**
  827. * Θ
  828. */
  829. THETA:"Θ",
  830. /**
  831. * Ω
  832. */
  833. OMEGA:"Ω",
  834. /**
  835. * δ
  836. */
  837. DELTA:"δ",
  838. /**
  839. * φ
  840. */
  841. PHI_LOWER:"φ",
  842. /**
  843. * ε
  844. */
  845. EPSILON:"ε"
  846. }
  847. /**@namespace String utilities*/
  848. 1comb.string = {
  849. /**@lends comb.string*/
  850. /**
  851. * Pads a string
  852. *
  853. * @example
  854. *
  855. * comb.string.pad("STR", 5, " ", true) => "STR "
  856. * comb.string.pad("STR", 5, "$") => "$$STR"
  857. *
  858. * @param {String} string the string to pad
  859. * @param {Number} length the length of the string when padded
  860. * @param {String} [ch= " "] character to pad the string with
  861. * @param {Boolean} [end=false] if true then the padding is added to the end
  862. *
  863. * @returns {String} the padded string
  864. */
  865. pad:function (string, length, ch, end) {
  866. 318 string = "" + string; //check for numbers
  867. 318 ch = ch || " ";
  868. 318 var strLen = string.length;
  869. 318 while (strLen < length) {
  870. 204 if (end) {
  871. 117 string += ch;
  872. } else {
  873. 87 string = ch + string;
  874. }
  875. 204 strLen++;
  876. }
  877. 318 return string;
  878. },
  879. /**
  880. * Truncates a string to the specified length.
  881. * @example
  882. *
  883. * //from the beginning
  884. * comb.string.truncate("abcdefg", 3) => "abc";
  885. * //from the end
  886. * comb.string.truncate("abcdefg", 3,true) => "efg"
  887. * //omit the length
  888. * comb.string.truncate("abcdefg") => "abcdefg"
  889. *
  890. * @param {String} string the string to truncate
  891. * @param {Number} [length = -1] the max length of the string, if the string is
  892. * shorter than the length then the string is returned.
  893. * @param {Boolean} [end=false] truncate starting at the end of the string
  894. *
  895. * @return {String} the truncated string.
  896. */
  897. truncate:function (string, length, end) {
  898. 39 var ret = string;
  899. 39 if (comb.isString(ret)) {
  900. 37 if (string.length > length) {
  901. 20 if (end) {
  902. 5 var l = string.length;
  903. 5 ret = string.substring(l - length, l);
  904. } else {
  905. 15 ret = string.substring(0, length);
  906. }
  907. }
  908. } else {
  909. 2 ret = comb.string.truncate("" + ret, length);
  910. }
  911. 39 return ret;
  912. },
  913. /**
  914. * Formats a string with the specified format
  915. *
  916. * @example
  917. *
  918. * var format = comb.string.format;
  919. *
  920. * format("%s, %s", ["Hello", "World"]) => "Hello, World";
  921. * format("%[ 10]s, %[- 10]s", ["Hello", "World"])
  922. * => " Hello, World ";
  923. * format("%-!10s, %#10s, %10s and %-10s",
  924. * "apple", "orange", "bananas", "watermelons")
  925. * => "apple!!!!!, ####orange, bananas and watermelon"
  926. * format("%+d, %+d, %10d, %-10d, %-+#10d, %10d",
  927. * 1,-2, 1, 2, 3, 100000000000)
  928. * => "+1, -2, 0000000001, 2000000000, +3########, 1000000000"
  929. * format("%[h:mm a]D", [date]) => 7:32 PM - local -
  930. * format("%[h:mm a]Z", [date]) => 12:32 PM - UTC
  931. * //When using object formats they must be in an array otherwise
  932. * //format will try to interpolate the properties into the string.
  933. * format("%j", [{a : "b"}])
  934. * => '{"a":"b"}'
  935. * format("%1j, %4j", [{a : "b"}, {a : "b"}])
  936. * => '{\n "a": "b"\n},\n{\n "a": "b"\n}'
  937. * format("{hello}, {world}", {hello : "Hello", world : "World")
  938. * => "Hello, World";
  939. * format({[-s10]apple}, {[%#10]orange}, {[10]banana} and {[-10]watermelons}",
  940. * {
  941. * apple : "apple",
  942. * orange : "orange",
  943. * banana : "bananas",
  944. * watermelons : "watermelons"
  945. * });
  946. * => applesssss, ####orange, bananas and watermelon
  947. *
  948. * @param {String} str the string to format, if you want to use a spacing character as padding (other than \\s) then put your format in brackets.
  949. * <ol>
  950. * <li>String Formats %[options]s</li>
  951. * <ul>
  952. * <li>- : left justified</li>
  953. * <li>Char : padding character <b>Excludes d,j,s</b></li>
  954. * <li>Number : width</li>
  955. * </ul>
  956. * </li>
  957. * <li>Number Formats %[options]d</li>
  958. * <ul>
  959. * <li>- : left justified</li>
  960. * <li>+ : signed number</li>
  961. * <li>Char : padding character <b>Excludes d,j,s</b></li>
  962. * <li>Number : width</li>
  963. * </ul>
  964. * </li>
  965. * <li>Object Formats %[options]j</li>
  966. * <ul>
  967. * <li>Number : spacing for object properties.</li>
  968. * </ul>
  969. * </li>
  970. * </ol>
  971. *
  972. *
  973. * @param {Object|Array|Arguments...} obj the parameters to replace in the string
  974. * if an array is passed then the array is used sequentially
  975. * if an object is passed then the object keys are used
  976. * if a variable number of args are passed then they are used like an array
  977. *
  978. * @returns {String} the formatted string
  979. */
  980. format:function (str, obj) {
  981. 90 !date && (date = require("./date"));
  982. 90 if (obj instanceof Array) {
  983. 33 var i = 0, len = obj.length;
  984. //find the matches
  985. 33 return str.replace(FORMAT_REGEX, function (m, format, type) {
  986. 53 var replacer, ret;
  987. 53 if (i < len) {
  988. 52 replacer = obj[i++];
  989. } else {
  990. //we are out of things to replace with so
  991. //just return the match?
  992. 1 return m;
  993. }
  994. 52 if (m == "%s" || m == "%d" || m == "%D") {
  995. //fast path!
  996. 27 ret = replacer;
  997. 25 } else if (m == "%Z") {
  998. 1 ret = replacer.toUTCString();
  999. 24 } else if (m == "%j") {
  1000. 2 try {
  1001. 2 ret = JSON.stringify(replacer);
  1002. } catch (e) {
  1003. 1 throw new Error("comb.string.format : Unable to parse json from ", replacer);
  1004. }
  1005. } else {
  1006. 22 format = format.replace(/^\[|\]$/g, "");
  1007. 22 switch (type) {
  1008. case "s":
  1009. 4 ret = formatString(replacer, format);
  1010. 4 break;
  1011. case "d":
  1012. 7 ret = formatNumber(replacer, format);
  1013. 6 break;
  1014. case "j":
  1015. 3 ret = formatObject(replacer, format);
  1016. 2 break;
  1017. case "D":
  1018. 4 ret = date.date.format(replacer, format);
  1019. 4 break;
  1020. case "Z":
  1021. 4 ret = date.date.format(replacer, format, true);
  1022. 4 break;
  1023. }
  1024. }
  1025. 49 return ret;
  1026. });
  1027. 57 } else if (typeof obj == "object") {
  1028. 34 return str.replace(INTERP_REGEX, function (m, format, value) {
  1029. 135 value = obj[value];
  1030. 135 if (!misc.isUndefined(value)) {
  1031. 134 if (format) {
  1032. 82 if (comb.isString(value)) {
  1033. 54 return formatString(value, format);
  1034. 28 } else if (typeof value == "number") {
  1035. 1 return formatNumber(value, format);
  1036. 27 } else if (date.isDate(value)) {
  1037. 26 return date.date.format(value, format);
  1038. 1 } else if (typeof value == "object") {
  1039. 1 return formatObject(value, format);
  1040. }
  1041. } else {
  1042. 52 return "" + value;
  1043. }
  1044. }
  1045. 1 return m;
  1046. });
  1047. } else {
  1048. 23 var args = Array.prototype.slice.call(arguments).slice(1);
  1049. 23 return exports.string.format(str, args);
  1050. }
  1051. },
  1052. /**
  1053. * Converts a string to an array
  1054. *
  1055. * @example
  1056. *
  1057. * comb.string.toArray("a|b|c|d", "|") => ["a","b","c","d"]
  1058. * comb.string.toArray("a", "|") => ["a"]
  1059. * comb.string.toArray("", "|") => []
  1060. *
  1061. * @param {String} str the string to parse
  1062. * @param {String} delimeter the delimeter to use
  1063. */
  1064. toArray:function (testStr, delim) {
  1065. 3 var ret = [];
  1066. 3 if (testStr) {
  1067. 3 if (testStr.indexOf(delim) > 0) return testStr.replace(/\s+/g, "").split(delim);
  1068. 1 else return [testStr];
  1069. }
  1070. 1 return ret;
  1071. },
  1072. /**
  1073. * Returns a string duplicated n times;
  1074. *
  1075. * @example
  1076. *
  1077. * comb.string.multiply("HELLO", 5) => "HELLOHELLOHELLOHELLOHELLO"
  1078. *
  1079. *
  1080. */
  1081. multiply:function (str, times) {
  1082. 152 var ret = [];
  1083. 152 if (times) {
  1084. 147 for (var i = 0; i < times; i++) {
  1085. 562 ret.push(str);
  1086. }
  1087. }
  1088. 152 return ret.join("");
  1089. },
  1090. /**
  1091. * Styles a string according to the specified styles.
  1092. *
  1093. * @example
  1094. * //style a string red
  1095. * comb.string.style('myStr', 'red');
  1096. * //style a string red and bold
  1097. * comb.string.style('myStr', ['red', bold]);
  1098. *
  1099. * @param {String} str The string to style.
  1100. * @param {String|Array} styles the style or styles to apply to a string.
  1101. * options include :
  1102. * <ul>
  1103. * <li>bold</li>
  1104. * <li>bright</li>
  1105. * <li>italic</li>
  1106. * <li>underline</li>
  1107. * <li>inverse</li>
  1108. * <li>crossedOut</li>
  1109. * <li>blink</li>
  1110. * <li>red</li>
  1111. * <li>green</li>
  1112. * <li>yellow</li>
  1113. * <li>blue</li>
  1114. * <li>magenta</li>
  1115. * <li>cyan</li>
  1116. * <li>white</li>
  1117. * <li>redBackground</li>
  1118. * <li>greenBackground</li>
  1119. * <li>yellowBackground</li>
  1120. * <li>blueBackground</li>
  1121. * <li>magentaBackground</li>
  1122. * <li>cyanBackground</li>
  1123. * <li>whiteBackground</li>
  1124. * <li>grey</li>
  1125. * <li>black</li>
  1126. *
  1127. * </ul>
  1128. */
  1129. style:function (str, options) {
  1130. 48 var ret = str;
  1131. 48 if (options) {
  1132. 48 if (ret instanceof Array) {
  1133. 1 ret = ret.map(function (s) {
  1134. 3 return comb.string.style(s, options);
  1135. })
  1136. 47 } else if (options instanceof Array) {
  1137. 4 options.forEach(function (option) {
  1138. 12 ret = comb.string.style(ret, option);
  1139. });
  1140. 43 } else if (options in styles) {
  1141. 43 ret = '\x1B[' + styles[options] + 'm' + str + '\x1B[0m';
  1142. }
  1143. }
  1144. 48 return ret;
  1145. }
  1146. };
collections/AVLTree.js
Coverage100.00 SLOC203 LOC100 Missed0
  1. 1var define = require("../define").define,
  2. Tree = require("./Tree"),
  3. base = require("../base"),
  4. multiply = base.string.multiply;
  5. 1var abs = Math.abs;
  6. 1var makeNode = function(data) {
  7. 122 return {
  8. data : data,
  9. balance : 0,
  10. left : null,
  11. right : null
  12. }
  13. };
  14. 1var rotateSingle = function(root, dir, otherDir) {
  15. 80 var save = root[otherDir];
  16. 80 root[otherDir] = save[dir];
  17. 80 save[dir] = root;
  18. 80 return save;
  19. };
  20. 1var rotateDouble = function(root, dir, otherDir) {
  21. 15 root[otherDir] = rotateSingle(root[otherDir], otherDir, dir);
  22. 15 return rotateSingle(root, dir, otherDir);
  23. };
  24. 1var adjustBalance = function(root, dir, bal) {
  25. 15 var otherDir = dir == "left" ? "right" : "left";
  26. 15 var n = root[dir], nn = n[otherDir];
  27. 15 if (nn.balance == 0)
  28. 11 root.balance = n.balance = 0;
  29. 4 else if (nn.balance == bal) {
  30. 2 root.balance = -bal;
  31. 2 n.balance = 0;
  32. }
  33. else { /* nn.balance == -bal */
  34. 2 root.balance = 0;
  35. 2 n.balance = bal;
  36. }
  37. 15 nn.balance = 0;
  38. };
  39. 1var insertAdjustBalance = function(root, dir) {
  40. 60 var otherDir = dir == "left" ? "right" : "left";
  41. 60 var n = root[dir];
  42. 60 var bal = dir == "left" ? -1 : +1;
  43. 60 if (n.balance == bal) {
  44. 46 root.balance = n.balance = 0;
  45. 46 root = rotateSingle(root, otherDir, dir);
  46. }
  47. else {
  48. 14 adjustBalance(root, dir, bal);
  49. 14 root = rotateDouble(root, otherDir, dir);
  50. }
  51. 60 return root;
  52. };
  53. 1var removeAdjustBalance = function(root, dir, done) {
  54. 5 var otherDir = dir == "left" ? "right" : "left";
  55. 5 var n = root[otherDir];
  56. 5 var bal = dir == "left" ? -1 : 1;
  57. 5 if (n.balance == -bal) {
  58. 1 root.balance = n.balance = 0;
  59. 1 root = rotateSingle(root, dir, otherDir);
  60. }
  61. 4 else if (n.balance == bal) {
  62. 1 adjustBalance(root, otherDir, -bal);
  63. 1 root = rotateDouble(root, dir, otherDir);
  64. }
  65. else { /* n.balance == 0 */
  66. 3 root.balance = -bal;
  67. 3 n.balance = bal;
  68. 3 root = rotateSingle(root, dir, otherDir);
  69. 3 done.done = true;
  70. }
  71. 5 return root;
  72. };
  73. 1var insert = function(root, data, done, compare) {
  74. 472 if (root == null || root == undefined)
  75. 122 root = makeNode(data);
  76. else {
  77. 350 var dir = compare(data, root.data) == -1 ? "left" : "right";
  78. 350 root[dir] = insert(root[dir], data, done, compare);
  79. 350 if (!done.done) {
  80. /* Update balance factors */
  81. 255 root.balance += dir == "left" ? -1 : 1;
  82. /* Rebalance as necessary and terminate */
  83. 255 if (root.balance == 0)
  84. 21 done.done = true;
  85. 234 else if (abs(root.balance) > 1) {
  86. 60 root = insertAdjustBalance(root, dir);
  87. 60 done.done = true;
  88. }
  89. }
  90. }
  91. 472 return root;
  92. };
  93. 1var remove = function(root, data, done, compare) {
  94. 57 var dir, cmp, save, b;
  95. 57 if (root) {
  96. //Remove node
  97. 57 cmp = compare(data, root.data);
  98. 57 if (cmp === 0) {
  99. // Unlink and fix parent
  100. 26 var l = root.left, r = root.right;
  101. 26 if (!l || !r) {
  102. 18 dir = !l ? "right" : "left";
  103. 18 save = root[dir];
  104. 18 return save;
  105. }
  106. else {
  107. 8 var heir = l, r;
  108. 8 while ((r = heir.right) != null) {
  109. 3 heir = r;
  110. }
  111. 8 root.data = heir.data;
  112. //reset and start searching
  113. 8 data = heir.data;
  114. }
  115. }
  116. 39 dir = compare(root.data, data) == -1 ? "right" : "left";
  117. 39 root[dir] = remove(root[dir], data, done, compare);
  118. 39 if (!done.done) {
  119. /* Update balance factors */
  120. 24 b = (root.balance += (dir == "left" ? 1 : -1));
  121. /* Terminate or rebalance as necessary */
  122. 24 var a = abs(b);
  123. 24 if (a === 1)
  124. 10 done.done = true;
  125. 14 else if (a > 1)
  126. 5 root = removeAdjustBalance(root, dir, done);
  127. }
  128. }
  129. 39 return root;
  130. };
  131. /**
  132. * @ignoreCode
  133. * @class <p>An AVL tree is a self-balancing binary search tree.
  134. * In an AVL tree, the heights of the two child subtrees of any node differ by at most one.
  135. * Lookup, insertion, and deletion all take O(log n) time in both the average and worst cases,
  136. * where n is the number of nodes in the tree prior to the operation.
  137. * Insertions and deletions may require the tree to be rebalanced by one or more tree rotations.</p>
  138. * <p>AVL trees are more rigidly balanced than red-black trees, leading to slower insertion and removal but faster retrieval</p>
  139. *
  140. * <b>Performance</b>
  141. * <table>
  142. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  143. * <tr><td>Space</td><td>O(n)</td><td>O(n)</td></tr>
  144. * <tr><td>Search</td><td>O(log n)</td><td>O(log n)</td></tr>
  145. * <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
  146. * <tr><td>Delete</td><td>O(log n)</td><td>O(log n)</td></tr>
  147. * <table>
  148. * @name AVLTree
  149. * @augments comb.collections.Tree
  150. * @memberOf comb.collections
  151. */
  152. 1module.exports = exports = define(Tree, {
  153. instance : {
  154. /**@lends comb.collections.AVLTree.prototype*/
  155. insert : function(data) {
  156. 122 var done = {done : false};
  157. 122 this.__root = insert(this.__root, data, done, this.compare);
  158. },
  159. remove : function(data) {
  160. 18 this.__root = remove(this.__root, data, {done : false}, this.compare);
  161. },
  162. __printNode : function(node, level) {
  163. 37 var str = [];
  164. 37 if (node == null) {
  165. 19 str.push(multiply('\t', level));
  166. 19 str.push("~");
  167. 19 console.log(str.join(""));
  168. } else {
  169. 18 this.__printNode(node.right, level + 1);
  170. 18 str.push(multiply('\t', level));
  171. 18 str.push(node.data + ":" + node.balance + "\n");
  172. 18 console.log(str.join(""));
  173. 18 this.__printNode(node.left, level + 1);
  174. }
  175. }
  176. }
  177. });
collections/AnderssonTree.js
Coverage100.00 SLOC165 LOC76 Missed0
  1. 1var define = require("../define").define,
  2. Tree = require("./Tree"),
  3. base = require("../base"),
  4. multiply = base.string.multiply;
  5. 1var RED = "red", BLACK = "black";
  6. 1var nil = {level:0, data:null};
  7. 1var makeNode = function (data, level) {
  8. 122 return {
  9. data:data,
  10. level:level,
  11. left:nil,
  12. right:nil
  13. }
  14. };
  15. 1var skew = function (root) {
  16. 384 if (root.level != 0 && root.left.level == root.level) {
  17. 66 var save = root.left;
  18. 66 root.left = save.right;
  19. 66 save.right = root;
  20. 66 root = save;
  21. }
  22. 384 return root;
  23. };
  24. 1var split = function (root) {
  25. 384 if (root.level != 0 && root.right.right.level == root.level) {
  26. 79 var save = root.right;
  27. 79 root.right = save.left;
  28. 79 save.left = root;
  29. 79 root = save;
  30. 79 ++root.level;
  31. }
  32. 384 return root;
  33. };
  34. 1var insert = function (root, data, compare) {
  35. 492 if (root == nil) {
  36. 122 root = makeNode(data, 1);
  37. }
  38. else {
  39. 370 var dir = compare(data, root.data) == -1 ? "left" : "right";
  40. 370 root[dir] = insert(root[dir], data, compare);
  41. 370 root = skew(root);
  42. 370 root = split(root);
  43. }
  44. 492 return root;
  45. };
  46. 1var remove = function (root, data, compare) {
  47. 59 var rLeft, rRight;
  48. 59 if (root != nil) {
  49. 59 var cmp = compare(data, root.data);
  50. 59 if (cmp == 0) {
  51. 26 rLeft = root.left, rRight = root.right;
  52. 26 if (rLeft != nil && rRight != nil) {
  53. 8 var heir = rLeft;
  54. 8 while (heir.right != nil)
  55. 3 heir = heir.right;
  56. 8 root.data = heir.data;
  57. 8 root.left = remove(rLeft, heir.data, compare);
  58. } else {
  59. 18 root = root[rLeft == nil ? "right" : "left"];
  60. }
  61. } else {
  62. 33 var dir = cmp == -1 ? "left" : "right";
  63. 33 root[dir] = remove(root[dir], data, compare);
  64. }
  65. }
  66. 59 if (root != nil) {
  67. 42 var rLevel = root.level;
  68. 42 var rLeftLevel = root.left.level, rRightLevel = root.right.level;
  69. 42 if (rLeftLevel < rLevel - 1 || rRightLevel < rLevel - 1) {
  70. 14 if (rRightLevel > --root.level)
  71. 2 root.right.level = root.level;
  72. 14 root = skew(root);
  73. 14 root = split(root);
  74. }
  75. }
  76. 59 return root;
  77. };
  78. /**
  79. *
  80. * @ignoreCode
  81. * @class <p>Andersson Trees are a version of a balanced Binary tree, while similar to RedBlack Trees the balancing is not as strict.</p>
  82. *
  83. * <b>Performance</b>
  84. * <table>
  85. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  86. * <tr><td>space</td><td>O(n)</td><td>O(n)</td></tr>
  87. * <tr><td>Search</td><td>O(log n)</td><td>O(log n)</td></tr>
  88. * <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
  89. * <tr><td>Delete</td><td>O(log n)</td><td>O(log n)</td></tr>
  90. * <table>
  91. * @name AnderssonTree
  92. * @augments comb.collections.Tree
  93. * @memberOf comb.collections
  94. */
  95. 1module.exports = exports = define(Tree, {
  96. instance:{
  97. /**@lends comb.collections.AnderssonTree.prototype*/
  98. isEmpty:function () {
  99. 4 return this.__root == nil || this._super(arguments);
  100. },
  101. insert:function (data) {
  102. 130 if (this.__root == null) this.__root = nil;
  103. 122 this.__root = insert(this.__root, data, this.compare);
  104. },
  105. remove:function (data) {
  106. 18 this.__root = remove(this.__root, data, this.compare);
  107. },
  108. traverseWithCondition:function (node, order, callback) {
  109. 614 var cont = true;
  110. 614 if (node != nil) {
  111. 317 return this._super(arguments);
  112. }
  113. 297 return cont;
  114. },
  115. traverse:function (node, order, callback) {
  116. 989 if (node != nil) {
  117. 482 this._super(arguments);
  118. }
  119. },
  120. contains:function (value) {
  121. 44 if (this.__root != nil) {
  122. 26 return this._super(arguments);
  123. }
  124. 18 return false;
  125. },
  126. __printNode:function (node, level) {
  127. 37 var str = [];
  128. 37 if (node.data == null || node == null) {
  129. 19 str.push(multiply('\t', level));
  130. 19 str.push("~");
  131. 19 console.log(str.join(""));
  132. } else {
  133. 18 this.__printNode(node.right, level + 1);
  134. 18 str.push(multiply('\t', level));
  135. 18 str.push(node.data + ":" + node.level + "\n");
  136. 18 console.log(str.join(""));
  137. 18 this.__printNode(node.left, level + 1);
  138. }
  139. }
  140. }
  141. });
collections/BinaryTree.js
Coverage100.00 SLOC83 LOC30 Missed0
  1. 1var define = require("../define").define,
  2. Tree = require("./Tree"),
  3. base = require("../base");
  4. /**
  5. *
  6. * @ignoreCode
  7. * @class <p>A Search tree that maintains the following properties</p>
  8. * <ul>
  9. * <li>The left subtree of a node contains only nodes with keys less than the node's key.
  10. * <li>The right subtree of a node contains only nodes with keys greater than the node's key.
  11. * <li>Both the left and right subtrees must also be binary search trees.
  12. * </ul>
  13. *
  14. * <b>Performance</b>
  15. * <table>
  16. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  17. * <tr><td>Space</td><td>O(n)</td><td>O(n)</td></tr>
  18. * <tr><td>Search</td><td>O(log n)</td><td>O(n)</td></tr>
  19. * <tr><td>Insert</td><td>O(log n)</td><td>O(n)</td></tr>
  20. * <tr><td>Delete</td><td>O(log n)</td><td>O(n)</td></tr>
  21. * <table>
  22. * @name BinaryTree
  23. * @augments comb.collections.Tree
  24. * @memberOf comb.collections
  25. */
  26. 1module.exports = exports = define(Tree, {
  27. instance : {
  28. /**@lends comb.collections.BinaryTree.prototype*/
  29. insert : function(data) {
  30. 124 if (this.__root == null) {
  31. 9 return (this.__root = {
  32. data : data,
  33. parent : null,
  34. left : null,
  35. right : null
  36. });
  37. }
  38. 115 var compare = this.compare;
  39. 115 var root = this.__root;
  40. 115 while (root != null) {
  41. 505 var cmp = compare(data, root.data);
  42. 505 if (cmp) {
  43. 503 var leaf = (cmp == -1) ? "left" : "right";
  44. 503 var next = root[leaf];
  45. 503 if (next == null) {
  46. 113 return (root[leaf] = {data : data, parent : root, left : null, right : null});
  47. } else {
  48. 390 root = next;
  49. }
  50. } else {
  51. 2 return;
  52. }
  53. }
  54. },
  55. remove : function(data) {
  56. 19 if (this.__root != null) {
  57. 19 var head = {right : this.__root}, it = head;
  58. 19 var p, f = null;
  59. 19 var dir = "right";
  60. 19 while (it[dir] != null) {
  61. 51 p = it;
  62. 51 it = it[dir];
  63. 51 var cmp = this.compare(data, it.data);
  64. 51 if (!cmp) {
  65. 18 f = it;
  66. }
  67. 51 dir = (cmp == -1 ? "left" : "right");
  68. }
  69. 19 if (f != null) {
  70. 18 f.data = it.data;
  71. 18 p[p.right == it ? "right" : "left"] = it[it.left == null ? "right" : "left"];
  72. }
  73. 19 this.__root = head.right;
  74. }
  75. }
  76. }
  77. });
collections/Collection.js
Coverage100.00 SLOC56 LOC8 Missed0
  1. 1var define = require("../define").define,
  2. base = require("../base");
  3. /**
  4. * @ignoreCode
  5. * @class Base class for all collections
  6. * @name Collection
  7. * @memberOf comb.collections
  8. */
  9. 1define(null, {
  10. instance:{
  11. /**@lends comb.collections.Collection.prototype*/
  12. /**
  13. * Concats two collections
  14. */
  15. concat:function () {
  16. 1 throw new Error("Not Implemented");
  17. },
  18. /**
  19. * Joins two collections
  20. */
  21. join:function () {
  22. 1 throw new Error("Not Implemented");
  23. },
  24. /**
  25. * Slice a portion from a collection
  26. */
  27. slice:function () {
  28. 1 throw new Error("Not Implemented");
  29. },
  30. /**
  31. * Convert a collection to a string
  32. */
  33. toString:function () {
  34. 1 throw new Error("Not Implemented");
  35. },
  36. /**
  37. * Find the index of an item in a collection
  38. */
  39. indexOf:function () {
  40. 1 throw new Error("Not Implemented");
  41. },
  42. /**
  43. * Find the last index of an item in a collection
  44. */
  45. lastIndexOf:function () {
  46. 1 throw new Error("Not Implemented");
  47. }
  48. }
  49. }).as(module);
collections/HashTable.js
Coverage100.00 SLOC378 LOC126 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. Iterable = require("./Iterable"),
  4. base = require("../base");
  5. 1var hashFunction = function (key) {
  6. 100 if (typeof key == "string") {
  7. 26 return key;
  8. 74 } else if (typeof key == "object") {
  9. 44 return key.hashCode ? key.hashCode() : "" + key;
  10. } else {
  11. 30 return "" + key;
  12. }
  13. };
  14. 1var Bucket = define(null, {
  15. instance:{
  16. constructor:function () {
  17. 32 this.__entries = [];
  18. },
  19. pushValue:function (key, value) {
  20. 36 this.__entries.push({key:key, value:value});
  21. 36 return value;
  22. },
  23. remove:function (key) {
  24. 14 var ret = null, map = this.__entries, val;
  25. 14 var i = map.length - 1;
  26. 14 for (; i >= 0; i--) {
  27. 16 if ((val = map[i]) != null && val.key === key) {
  28. 12 map[i] = null;
  29. 12 return val.value;
  30. }
  31. }
  32. 2 return ret;
  33. },
  34. "set":function (key, value) {
  35. 14 var ret = null, map = this.__entries;
  36. 14 var i = map.length - 1;
  37. 14 for (; i >= 0; i--) {
  38. 18 var val = map[i];
  39. 18 if (val && key === val.key) {
  40. 6 val.value = value;
  41. 6 ret = value;
  42. 6 break;
  43. }
  44. }
  45. 14 if (!ret) {
  46. 8 map.push({key:key, value:value});
  47. }
  48. 14 return ret;
  49. },
  50. find:function (key) {
  51. 34 var ret = null, map = this.__entries, val;
  52. 34 var i = map.length - 1;
  53. 34 for (; i >= 0; i--) {
  54. 43 val = map[i];
  55. 43 if (val && key === val.key) {
  56. 26 ret = val.value;
  57. 26 break;
  58. }
  59. }
  60. 34 return ret;
  61. },
  62. getEntrySet:function (arr) {
  63. 78 var map = this.__entries, l = map.length;
  64. 78 if (l) {
  65. 78 for (var i = 0; i < l; i++) {
  66. 88 var e = map[i];
  67. 88 if (e) {
  68. 80 arr.push(e);
  69. }
  70. }
  71. }
  72. },
  73. getKeys:function (arr) {
  74. 21 var map = this.__entries, l = map.length;
  75. 21 if (l) {
  76. 21 for (var i = 0; i < l; i++) {
  77. 24 var e = map[i];
  78. 24 if (e) {
  79. 8 arr.push(e.key);
  80. }
  81. }
  82. }
  83. 21 return arr;
  84. },
  85. getValues:function (arr) {
  86. 14 var map = this.__entries, l = map.length;
  87. 14 if (l) {
  88. 14 for (var i = 0; i < l; i++) {
  89. 16 var e = map[i];
  90. 16 if (e) {
  91. 8 arr.push(e.value);
  92. }
  93. }
  94. }
  95. 14 return arr;
  96. }
  97. }
  98. });
  99. /**
  100. * @ignoreCode
  101. * @class <p>Implementation of a HashTable for javascript.
  102. * This HashTable implementation allows one to use anything as a key.
  103. * </p>
  104. * <b>NOTE: THIS IS ~ 3 times slower than javascript native objects</b>
  105. *
  106. * <p> A use case for this collection is when one needs to store items in which the key will not be a string, or number</p>
  107. *
  108. * @name HashTable
  109. * @augments comb.collections.Collection
  110. * @memberOf comb.collections
  111. *
  112. * @property {Array} keys all keys contained in the table
  113. * @property {Array} values all values contained in the table
  114. * @property {Array} entrySet an array of objects. Each object contains a key, and value property.
  115. */
  116. 1define([Collection, Iterable], {
  117. instance:{
  118. /**@lends comb.collections.HashTable.prototype*/
  119. constructor:function () {
  120. 13 this.__map = {};
  121. },
  122. __entrySet:function () {
  123. 14 var ret = [], es = [];
  124. 14 for (var i in this.__map) {
  125. 78 this.__map[i].getEntrySet(ret);
  126. }
  127. 14 return ret;
  128. },
  129. /**
  130. * Put a key, value pair into the table
  131. *
  132. * <b>NOTE :</b> the collection will not check if the key previously existed.
  133. *
  134. * @param {Anything} key the key to look up the object.
  135. * @param {Anything} value the value that corresponds to the key.
  136. *
  137. * @returns the value
  138. */
  139. put:function (key, value) {
  140. 29 var hash = hashFunction(key);
  141. 29 var bucket = null;
  142. 29 if ((bucket = this.__map[hash]) == null) {
  143. 25 bucket = (this.__map[hash] = new Bucket());
  144. }
  145. 29 bucket.pushValue(key, value);
  146. 29 return value;
  147. },
  148. /**
  149. * Remove a key value pair from the table.
  150. *
  151. * @param key the key of the key value pair to remove.
  152. *
  153. * @returns the removed value.
  154. */
  155. remove:function (key) {
  156. 16 var hash = hashFunction(key), ret = null;
  157. 16 var bucket = this.__map[hash];
  158. 16 if (bucket) {
  159. 14 ret = bucket.remove(key);
  160. }
  161. 16 return ret;
  162. },
  163. /**
  164. * Get the value corresponding to the key.
  165. *
  166. * @param key the key used to look up the value
  167. *
  168. * @returns null if not found, or the value.
  169. */
  170. "get":function (key) {
  171. 26 var hash = hashFunction(key), ret = null;
  172. 26 var bucket = null;
  173. 26 if ((bucket = this.__map[hash]) != null) {
  174. 26 ret = bucket.find(key);
  175. }
  176. 26 return ret;
  177. },
  178. /**
  179. * Set the value of a previously existing key,value pair or create a new entry.
  180. *
  181. * @param key the key to be be used
  182. * @param value the value to be set
  183. *
  184. * @returns the value.
  185. */
  186. "set":function (key, value) {
  187. 21 var hash = hashFunction(key), ret = null, bucket = null, map = this.__map;
  188. 21 if ((bucket = map[hash]) != null) {
  189. 14 ret = bucket.set(key, value);
  190. } else {
  191. 7 ret = (map[hash] = new Bucket()).pushValue(key, value);
  192. }
  193. 21 return ret;
  194. },
  195. /**
  196. * Tests if the table contains a particular key
  197. * @param key the key to test
  198. *
  199. * @returns {Boolean} true if it exitsts false otherwise.
  200. */
  201. contains:function (key) {
  202. 8 var hash = hashFunction(key), ret = false;
  203. 8 var bucket = null;
  204. 8 if ((bucket = this.__map[hash]) != null) {
  205. 8 ret = bucket.find(key) != null;
  206. }
  207. 8 return ret;
  208. },
  209. /**
  210. * Returns a new HashTable containing the values of this HashTable, and the other table.
  211. * </br>
  212. * <b> DOES NOT CHANGE THE ORIGINAL!</b>
  213. * @param {comb.collections.HashTable} hashTable the hash table to concat with this.
  214. *
  215. * @returns {comb.collections.HashTable} a new HashTable containing all values from both tables.
  216. */
  217. concat:function (hashTable) {
  218. 2 if (hashTable instanceof this._static) {
  219. 1 var ret = new this._static();
  220. 1 var otherEntrySet = hashTable.entrySet.concat(this.entrySet);
  221. 1 for (var i = otherEntrySet.length - 1; i >= 0; i--) {
  222. 4 var e = otherEntrySet[i];
  223. 4 ret.put(e.key, e.value);
  224. }
  225. 1 return ret;
  226. } else {
  227. 1 throw new TypeError("When joining hashtables the joining arg must be a HashTable");
  228. }
  229. },
  230. /**
  231. * Creates a new HashTable containg values that passed the filtering function.
  232. *
  233. * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field
  234. * @param {Object} scope the scope to call the function.
  235. *
  236. * @returns {comb.collections.HashTable} the HashTable containing the values that passed the filter.
  237. */
  238. filter:function (cb, scope) {
  239. 1 var es = this.__entrySet(), ret = new this._static();
  240. 1 es = es.filter.apply(es, arguments);
  241. 1 for (var i = es.length - 1; i >= 0; i--) {
  242. 4 var e = es[i];
  243. 4 ret.put(e.key, e.value);
  244. }
  245. 1 return ret;
  246. },
  247. /**
  248. * Loop through each value in the hashtable
  249. *
  250. * @param {Function} cb the function to call with an object containing a key and value field
  251. * @param {Object} scope the scope to call the funciton in
  252. */
  253. forEach:function (cb, scope) {
  254. 1 var es = this.__entrySet(), l = es.length, f = cb.bind(scope || this);
  255. 1 es.forEach.apply(es, arguments);
  256. },
  257. /**
  258. * Determines if every item meets the condition returned by the callback.
  259. *
  260. * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field
  261. * @param {Object} [scope=this] scope to call the function in
  262. *
  263. * @returns {Boolean} True if every item passed false otherwise
  264. */
  265. every:function () {
  266. 2 var es = this.__entrySet();
  267. 2 return es.every.apply(es, arguments);
  268. },
  269. /**
  270. * Loop through each value in the hashtable, collecting the value returned by the callback function.
  271. * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field
  272. * @param {Object} [scope=this] scope to call the function in
  273. *
  274. * @returns {Array} an array containing the mapped values.
  275. */
  276. map:function () {
  277. 1 var es = this.__entrySet(), ret = new this._static();
  278. 1 return es.map.apply(es, arguments);
  279. },
  280. /**
  281. * Determines if some items meet the condition returned by the callback.
  282. *
  283. * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field
  284. * @param {Object} [scope=this] scope to call the function in
  285. *
  286. * @returns {Boolean} True if some items passed false otherwise
  287. */
  288. some:function () {
  289. 2 var es = this.__entrySet();
  290. 2 return es.some.apply(es, arguments);
  291. },
  292. /**
  293. * Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
  294. *
  295. * @param {Function} callback Function to execute on each value in the array.
  296. * @param initialValue Value to use as the first argument to the first call of the callback..
  297. */
  298. reduce:function () {
  299. 1 var es = this.__entrySet();
  300. 1 return es.reduce.apply(es, arguments);
  301. },
  302. /**
  303. * Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.
  304. *
  305. * @param {Function} callback Function to execute on each value in the array.
  306. * @param initialValue Value to use as the first argument to the first call of the callback..
  307. */
  308. reduceRight:function () {
  309. 1 var es = this.__entrySet();
  310. 1 return es.reduceRight.apply(es, arguments);
  311. },
  312. /**
  313. * Clears out all items from the table.
  314. */
  315. clear:function () {
  316. 1 this.__map = {};
  317. },
  318. getters:{
  319. keys:function () {
  320. 8 var ret = [], es = [];
  321. 8 for (var i in this.__map) {
  322. 21 this.__map[i].getKeys(ret);
  323. }
  324. 8 return ret;
  325. },
  326. values:function () {
  327. 6 var ret = [], es = [];
  328. 6 for (var i in this.__map) {
  329. 14 this.__map[i].getValues(ret);
  330. }
  331. 6 return ret;
  332. },
  333. entrySet:function () {
  334. 5 return this.__entrySet();
  335. },
  336. isEmpty:function () {
  337. 2 return this.keys.length == 0;
  338. }
  339. }
  340. }
  341. }).as(module);
collections/Heap.js
Coverage100.00 SLOC218 LOC62 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. base = require("../base");
  4. 1var padding = function(char, numSpaces) {
  5. 9 var ret = [];
  6. 9 for (var i = 0; i < numSpaces; i++)
  7. 16 ret.push(char);
  8. 9 return ret.join("");
  9. };
  10. 1module.exports = exports = define(Collection, {
  11. instance : {
  12. /**@lends comb.collections.Heap.prototype*/
  13. __getParentIndex : function(index) {
  14. 192 return Math.floor((index - 1) / 2);
  15. },
  16. __getLeftChildIndex : function(index) {
  17. 28 return (index * 2) + 1;
  18. },
  19. __getRightChildIndex : function(index) {
  20. 28 return (index * 2) + 2;
  21. },
  22. __makeNode : function(key, value) {
  23. 137 return {key : key, value : value};
  24. },
  25. /**
  26. * Base class for Heap Implementations.
  27. *
  28. *
  29. * @constructs
  30. * @augments comb.collections.Collection
  31. * @memberOf comb.collections
  32. *
  33. * @property {Number} count the current number of elements.
  34. * @property {Array} keys the keys of all items in the heap.
  35. * @property {Array} values the values contained in the heap.
  36. * @property {Boolean} isEmpty true if the Heap is empty.
  37. */
  38. constructor : function() {
  39. 32 this.__heap = [];
  40. },
  41. /**
  42. * Insert a key value into the key
  43. * @param key
  44. * @param value
  45. */
  46. insert : function(key, value) {
  47. 138 if (!base.isString(key)) {
  48. 137 var l = this.__heap.push(this.__makeNode(key, value));
  49. 137 this.__upHeap(l - 1);
  50. } else {
  51. 1 throw TypeError("Invalid key");
  52. }
  53. },
  54. /**
  55. * Removes the root from the heap
  56. *
  57. * @returns the value of the root
  58. */
  59. remove : function() {
  60. 48 var ret = undefined, heap = this.__heap, l = heap.length;
  61. 48 if (l) {
  62. 48 ret = heap[0];
  63. 48 if (l == 1) {
  64. 12 heap.length = 0;
  65. } else {
  66. 36 heap[0] = heap.pop();
  67. 36 this.__downHeap(0);
  68. }
  69. }
  70. 48 return ret ? ret.value : ret;
  71. },
  72. /**
  73. * Gets he value of the root node with out removing it.
  74. *
  75. * @returns the value of the root
  76. */
  77. peek : function() {
  78. 33 var ret = undefined, heap = this.__heap, l = heap.length;
  79. 33 if (l) {
  80. 30 ret = heap[0];
  81. }
  82. 33 return ret ? ret.value : ret;
  83. },
  84. /**
  85. * Gets the key of the root node without removing it.
  86. *
  87. * @returns the key of the root
  88. */
  89. peekKey : function() {
  90. 6 var ret = undefined, heap = this.__heap, l = heap.length;
  91. 6 if (l) {
  92. 4 ret = heap[0];
  93. }
  94. 6 return ret ? ret.key : ret;
  95. },
  96. /**
  97. * Perform the heapify operation after the an
  98. * item as been added to the bottom of the heap.
  99. *
  100. * @param index the index in which the new item was added
  101. */
  102. __upHeap : function(index) {
  103. 1 throw Error("NOT IMPLEMENTED");
  104. },
  105. /**
  106. * Heapify the heap after the root has been removed
  107. *
  108. * @param index the index of the root
  109. */
  110. __downHeap : function(index) {
  111. 1 throw Error("NOT IMPLEMENTED");
  112. },
  113. /**
  114. *
  115. * Determine if the heap contains a particular key.
  116. *
  117. * @param key key to test.
  118. *
  119. * @returns {Boolean} true if the key is contained in this heap.
  120. */
  121. containsKey : function(key) {
  122. 15 var heap = this.__heap;
  123. 15 for (var i = heap.length - 1; i >= 0; i--) {
  124. 42 if (heap[i].key == key) {
  125. 12 return true;
  126. }
  127. }
  128. 3 return false;
  129. },
  130. /**
  131. *
  132. * Determine if the heap contains a particular value.
  133. *
  134. * @param value value to test.
  135. *
  136. * @returns {Boolean} true if the value is contained in this heap.
  137. */
  138. containsValue : function(value) {
  139. 15 var heap = this.__heap;
  140. 15 for (var i = heap.length - 1; i >= 0; i--) {
  141. 42 if (heap[i].value == value) {
  142. 12 return true;
  143. }
  144. }
  145. 3 return false;
  146. },
  147. /**
  148. * Empty the heap.
  149. */
  150. clear : function() {
  151. 9 this.__heap.length = 0;
  152. },
  153. __printNode : function(index, level) {
  154. //console.log(level);
  155. 9 var str = [], node = this.__heap[index];
  156. 9 if (node == null || node == undefined) {
  157. 5 str.push(padding('\t', level));
  158. 5 str.push("~");
  159. 5 console.log(str.join(""));
  160. } else {
  161. 4 this.__printNode(this.__getRightChildIndex(index), level + 1);
  162. 4 str.push(padding('\t', level));
  163. 4 str.push(node.key + " : " + node.value + "\n");
  164. 4 console.log(str.join(""));
  165. 4 this.__printNode(this.__getLeftChildIndex(index), level + 1);
  166. }
  167. },
  168. /**
  169. * Print the heap.
  170. */
  171. print : function() {
  172. 1 this.__printNode(0, 0);
  173. },
  174. getters : {
  175. count : function() {
  176. 12 return this.__heap.length;
  177. },
  178. keys : function() {
  179. 3 return this.__heap.map(function(n) {
  180. 12 return n.key;
  181. });
  182. },
  183. values : function() {
  184. 3 return this.__heap.map(function(n) {
  185. 12 return n.value;
  186. });
  187. },
  188. isEmpty : function() {
  189. 18 return this.__heap.length == 0;
  190. }
  191. }
  192. }
  193. });
collections/Iterable.js
Coverage100.00 SLOC63 LOC9 Missed0
  1. 1var define = require("../define").define,
  2. base = require("../base");
  3. /**
  4. * @ignoreCode
  5. * @class Base class for all collections
  6. * @name Iterable
  7. * @memberOf comb.collections
  8. */
  9. 1define(null, {
  10. instance:{
  11. /**@lends comb.collections.Iterable.prototype*/
  12. /**
  13. * Filter items from a collection
  14. */
  15. filter:function () {
  16. 1 throw new Error("Not Implemented");
  17. },
  18. /**
  19. * Loop through the items in a collection
  20. */
  21. forEach:function () {
  22. 1 throw new Error("Not Implemented");
  23. },
  24. /**
  25. * Determine if every item in a collection meets the criteria
  26. */
  27. every:function () {
  28. 1 throw new Error("Not Implemented");
  29. },
  30. /**
  31. * Map every item in a collection
  32. */
  33. map:function () {
  34. 1 throw new Error("Not Implemented");
  35. },
  36. /**
  37. * Determing if some items in a colleciton meet the criteria
  38. */
  39. some:function () {
  40. 1 throw new Error("Not Implemented");
  41. },
  42. /**
  43. * Reduce a collection
  44. */
  45. reduce:function () {
  46. 1 throw new Error("Not Implemented");
  47. },
  48. /**
  49. * Reduce a collection starting from the right most position
  50. */
  51. reduceRight:function () {
  52. 1 throw new Error("Not Implemented");
  53. }
  54. }
  55. }).as(module);
collections/MaxHeap.js
Coverage100.00 SLOC66 LOC25 Missed0
  1. 1var define = require("../define").define,
  2. Heap = require("./Heap"),
  3. base = require("../base");
  4. /**
  5. * @ignoreCode
  6. *
  7. * @class <p> Max Heap implementation, lowest value in heap is always at the root.</p>
  8. * </br>
  9. * <b>Performance</b>
  10. * <table>
  11. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  12. * <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
  13. * <tr><td>Remove</td><td>O(log n)</td><td>O(log n)</td></tr>
  14. * <tr><td>Peek</td><td>O(1)</td><td>O(1)</td></tr>
  15. * <tr><td>Contains</td><td>O(n)</td><td>O(n)</td></tr>
  16. * <table>
  17. * @name MaxHeap
  18. * @augments comb.collections.Heap
  19. * @memberOf comb.collections
  20. */
  21. 1exports = module.exports = define(Heap, {
  22. instance : {
  23. __upHeap : function(index) {
  24. 48 var heap = this.__heap;
  25. 48 var node = heap[index];
  26. 48 while (index >= 0) {
  27. 84 var parentIndex = this.__getParentIndex(index), parent = heap[parentIndex];
  28. 84 if (parent && parent.key < node.key) {
  29. 36 heap[index] = parent;
  30. 36 index = parentIndex;
  31. } else {
  32. 48 break;
  33. }
  34. }
  35. 48 heap[index] = node;
  36. },
  37. __downHeap : function(index) {
  38. 12 var heap = this.__heap;
  39. 12 var node = heap[index], length = heap.length;
  40. 12 while (index < Math.floor(length / 2)) {
  41. 8 var leftIndex = this.__getLeftChildIndex(index),
  42. rightIndex = this.__getRightChildIndex(index), left = heap[leftIndex], right = heap[rightIndex], child, childIndex;
  43. 8 if (rightIndex < length && right.key < left.key) {
  44. 4 childIndex = leftIndex;
  45. 4 child = left
  46. } else {
  47. 4 childIndex = leftIndex;
  48. 4 child = heap[leftIndex];
  49. }
  50. 8 if(child.key > node.key){
  51. 5 heap[index] = child;
  52. 5 index = childIndex;
  53. }else{
  54. 3 break;
  55. }
  56. }
  57. 12 heap[index] = node;
  58. }
  59. }
  60. });
collections/MinHeap.js
Coverage100.00 SLOC65 LOC26 Missed0
  1. 1var define = require("../define").define,
  2. Heap = require("./Heap"),
  3. base = require("../base");
  4. 1var floor = Math.floor, MinHeap;
  5. /**
  6. * @class <p> Min Heap implementation, lowest value in heap is always at the root.</p>
  7. * </br>
  8. * <b>Performance</b>
  9. * <table>
  10. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  11. * <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
  12. * <tr><td>Remove</td><td>O(log n)</td><td>O(log n)</td></tr>
  13. * <tr><td>Peek</td><td>O(1)</td><td>O(1)</td></tr>
  14. * <tr><td>Contains</td><td>O(n)</td><td>O(n)</td></tr>
  15. * <table>
  16. * @name MinHeap
  17. * @augments comb.collections.Heap
  18. * @memberOf comb.collections
  19. * @ignoreCode
  20. */
  21. 1module.exports = exports = define(Heap, {
  22. instance : {
  23. __upHeap : function(index) {
  24. 88 var heap = this.__heap;
  25. 88 var node = heap[index], key = node.key, gpi = this.__getParentIndex;
  26. 88 while (index >= 0) {
  27. 108 var parentIndex = gpi(index), parent = heap[parentIndex];
  28. 108 if (parent && parent.key > key) {
  29. 20 heap[index] = parent;
  30. 20 index = parentIndex;
  31. } else {
  32. 88 break;
  33. }
  34. }
  35. 88 heap[index] = node;
  36. },
  37. __downHeap : function(index) {
  38. 24 var heap = this.__heap;
  39. 24 var node = heap[index], key = node.key, length = heap.length, max = floor(length / 2), glci = this.__getLeftChildIndex, grci = this.__getRightChildIndex;
  40. 24 while (index < max) {
  41. 16 var leftIndex = glci(index),
  42. rightIndex = grci(index), left = heap[leftIndex], right = heap[rightIndex], child, childIndex;
  43. 16 if (rightIndex < length && right.key < left.key) {
  44. 2 childIndex = rightIndex;
  45. 2 child = right;
  46. } else {
  47. 14 childIndex = leftIndex;
  48. 14 child = left;
  49. }
  50. 16 if (child.key < key) {
  51. 10 heap[index] = child;
  52. 10 index = childIndex;
  53. } else {
  54. 6 break;
  55. }
  56. }
  57. 24 heap[index] = node;
  58. }
  59. }
  60. });
collections/Pool.js
Coverage100.00 SLOC162 LOC51 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. Queue = require("./Queue"),
  4. base = require("../base");
  5. /**
  6. * @class Base class for a pool.
  7. *
  8. * @name Pool
  9. * @memberOf comb.collections
  10. *
  11. * @property {Number} count the total number of objects in the pool, including free and in use objects.
  12. * @property {Number} freeCount the number of free objects in this pool.
  13. * @property {Number} inUseCount the number of objects in use in this pool.
  14. * @property {Number} [minObjects=0] the minimum number of objects this pool should contain.
  15. * @property {Number} [maxObjects=1] the maximum number of objects this pool should contain
  16. * @ignoreCode
  17. */
  18. 1exports = module.exports = define(null, {
  19. instance : {
  20. /**@lends comb.collections.Pool.prototype*/
  21. __minObjects : 0,
  22. __maxObjects : 1,
  23. constructor : function(options) {
  24. 5 options = options || {};
  25. 5 this.__freeObjects = new Queue();
  26. 5 this.__inUseObjects = [];
  27. 5 this.__minObjects = options.minObjects || 0;
  28. 5 this.__maxObjects = options.maxObjects || 1;
  29. 5 this.minObjects = this.__minObjects;
  30. 4 this.maxObjects = this.__maxObjects;
  31. },
  32. /**
  33. * Retrieves an object from this pool.
  34. * `
  35. * @return {*} an object to contained in this pool
  36. */
  37. getObject : function() {
  38. 19 var ret = undefined;
  39. 19 if (this.freeCount > 0) {
  40. 2 ret = this.__freeObjects.dequeue();
  41. 2 this.__inUseObjects.push(ret);
  42. 17 } else if (this.__maxObjects > this.count) {
  43. 12 ret = this.createObject();
  44. 12 this.__inUseObjects.push(ret);
  45. }
  46. 19 return ret;
  47. },
  48. /**
  49. * Returns an object to this pool. The object is validated before it is returned to the pool,
  50. * if the validation fails then it is removed from the pool;
  51. * @param {*} obj the object to return to the pool
  52. */
  53. returnObject : function(obj) {
  54. 8 if (this.validate(obj) && this.count <= this.__maxObjects) {
  55. 7 this.__freeObjects.enqueue(obj);
  56. 7 var index;
  57. 7 if ((index = this.__inUseObjects.indexOf(obj)) > -1)
  58. 7 this.__inUseObjects.splice(index, 1);
  59. } else {
  60. 1 this.removeObject(obj);
  61. }
  62. },
  63. /**
  64. * Removes an object from the pool, this can be overriden to provide any
  65. * teardown of objects that needs to take place.
  66. *
  67. * @param {*} obj the object that needs to be removed.
  68. *
  69. * @return {*} the object removed.
  70. */
  71. removeObject : function(obj) {
  72. 2 var index;
  73. 2 if (this.__freeObjects.contains(obj)) {
  74. 1 this.__freeObjects.remove(obj);
  75. 1 } else if ((index = this.__inUseObjects.indexOf(obj)) > -1) {
  76. 1 this.__inUseObjects.splice(index, 1);
  77. }
  78. //otherwise its not contained in this pool;
  79. 2 return obj;
  80. },
  81. /**
  82. * Validates an object in this pool.
  83. * </br>
  84. * <b>THIS SHOULD BE OVERRIDDEN TO VALIDATE</b>
  85. *
  86. * @param {*} obj the object to validate.
  87. */
  88. validate : function(obj) {
  89. 8 return true;
  90. },
  91. /**
  92. * Creates a new object for this pool.
  93. * * </br>
  94. * <b>THIS SHOULD BE OVERRIDDEN TO ADD THE CORRECT TYPE OF OBJECT</b>
  95. *
  96. * @return {Object} be default just creates an object.
  97. */
  98. createObject : function() {
  99. 16 return {};
  100. },
  101. setters : {
  102. minObjects : function(l) {
  103. 6 if (l <= this.__maxObjects) {
  104. 5 this.__minObjects = l;
  105. 5 var i;
  106. 5 if ((i = this.count) < l) {
  107. 1 while (i++ < l) {
  108. 4 this.__freeObjects.enqueue(this.createObject());
  109. }
  110. }
  111. } else {
  112. 1 throw "comb.collections.Pool : minObjects cannot be greater than maxObjects.";
  113. }
  114. },
  115. maxObjects : function(l) {
  116. 9 if (l >= this.__minObjects) {
  117. 8 this.__maxObjects = l;
  118. 8 var i = this.count, j = this.freeCount, fo = this.__freeObjects;
  119. 8 while (i > l && j >= 0) {
  120. 1 this.removeObject(fo.dequeue());
  121. 1 j--;
  122. 1 i--;
  123. }
  124. } else {
  125. 1 throw "comb.collections.Pool : maxObjects cannot be less than maxObjects.";
  126. }
  127. }
  128. },
  129. getters : {
  130. freeCount : function() {
  131. 41 return this.__freeObjects.count;
  132. },
  133. inUseCount : function() {
  134. 14 return this.__inUseObjects.length;
  135. },
  136. count : function() {
  137. 52 return this.__freeObjects.count + this.__inUseObjects.length;
  138. },
  139. minObjects : function() {
  140. 2 return this.__minObjects;
  141. },
  142. maxObjects : function() {
  143. 1 return this.__maxObjects;
  144. }
  145. }
  146. }
  147. })
collections/PriorityQueue.js
Coverage100.00 SLOC41 LOC6 Missed0
  1. 1var define = require("../define").define,
  2. MinHeap = require("./MinHeap"),
  3. base = require("../base");
  4. 1var PriorityQueue;
  5. /**
  6. * @class PriorityQueue Implementation where the value with the highest priority moves to the front
  7. * Priority starts at 0, and the greatest value being the lowest priority;
  8. * @name PriorityQueue
  9. * @augments comb.collections.MinHeap
  10. * @memberOf comb.collections
  11. * @ignoreCode
  12. */
  13. 1PriorityQueue = define(MinHeap, {
  14. instance : {
  15. /**@lends comb.collections.PriorityQueue.prototype*/
  16. /**
  17. * Adds the value with the specified priority to the queue
  18. *
  19. * @param {Number} priority the priority of the item
  20. * </br>
  21. * <b>0 = Highest, n = lowest</b>
  22. * @param value
  23. */
  24. enqueue : function(priority, value) {
  25. 44 return this.insert(priority, value);
  26. },
  27. /**
  28. * Removes the item with the highest priority from the queue
  29. *
  30. * @returns the value of the item
  31. */
  32. dequeue : function() {
  33. 16 return this.remove();
  34. }
  35. }
  36. });
  37. 1module.exports = exports = PriorityQueue;
collections/Queue.js
Coverage100.00 SLOC119 LOC32 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. base = require("../base");
  4. /**
  5. * @class <p>FIFO Data structure</p>
  6. * @name Queue
  7. * @augments comb.collections.Collection
  8. * @memberOf comb.collections
  9. *
  10. * @property {Number} count the current number of elements in this queue
  11. * @property {Boolean} isEmpty true if this queue is empty
  12. * @property {Array} values a copy of the values contained in this queue
  13. * @ignoreCode
  14. */
  15. 1module.exports = exports = define(Collection, {
  16. instance : {
  17. /**@lends comb.collections.Queue.prototype*/
  18. constructor : function() {
  19. 6 this.__queue = [];
  20. 6 this.__next = 0;
  21. 6 this.__last = 0;
  22. },
  23. /**
  24. * Add data to this queue
  25. * @param {*} data element to add
  26. */
  27. enqueue : function(data) {
  28. 27 this.__queue[this.__last++] = data;
  29. },
  30. /**
  31. * Removes first item from the head of the queue
  32. *
  33. * @return {*} The element removed from this queue. Returns undefined if the queue is empty.
  34. */
  35. dequeue : function() {
  36. 7 var ret = undefined,next = this.__next, queue;
  37. 7 if (next != this.__last) {
  38. 5 queue = this.__queue;
  39. 5 ret = queue[next];
  40. 5 queue[this.__next++] = undefined;
  41. }
  42. 7 return ret;
  43. },
  44. /**
  45. * Retrieves the item at the head of the queue without removing it
  46. *
  47. * @return {*} The element at the head of the queue. Returns undefined if the queue is empty.
  48. */
  49. peek : function() {
  50. 1 var ret = undefined, next = this.__next;
  51. 1 if (next != this.__last) {
  52. 1 ret = this.__queue[next];
  53. }
  54. 1 return ret;
  55. },
  56. /**
  57. * Removes all items from this queue
  58. */
  59. clear : function() {
  60. 1 this.__queue.length = 0;
  61. 1 this.__next = 0;
  62. 1 this.__last = 0;
  63. },
  64. /**
  65. * Determine if this queue contains the element
  66. * @param {*} obj the object to find
  67. *
  68. * @return {Boolean} true if this queue contains the element
  69. */
  70. contains : function(obj) {
  71. 5 return this.__queue.indexOf(obj) != -1;
  72. },
  73. /**
  74. * Removes an element from this queue.
  75. * @param {*} obj the data to remove.
  76. *
  77. * @return {Boolean} true if the element was removed, false otherwise.
  78. */
  79. remove : function(obj) {
  80. 8 var index = this.__queue.indexOf(obj), ret = false;
  81. 8 if (index != -1) {
  82. 8 if (index == this.__next) {
  83. 1 this.dequeue();
  84. } else {
  85. 7 this.__queue.splice(index, 1);
  86. 7 this.__last--;
  87. }
  88. 8 ret = true;
  89. }
  90. 8 return ret;
  91. },
  92. toString : function(){
  93. 3 return this.__queue.toString();
  94. },
  95. getters : {
  96. count : function() {
  97. 95 return this.__last - this.__next;
  98. },
  99. isEmpty : function() {
  100. 5 return this.__last - this.__next == 0;
  101. },
  102. values : function() {
  103. 1 return this.__queue.slice(this.__next, this.__last);
  104. }
  105. }
  106. }
  107. });
collections/RedBlackTree.js
Coverage100.00 SLOC188 LOC101 Missed0
  1. 1var define = require("../define").define,
  2. Tree = require("./Tree"),
  3. base = require("../base"),
  4. multiply = base.string.multiply;
  5. 1var RED = "red", BLACK = "black";
  6. 1var isRed = function(node) {
  7. 1156 return node != null && node.red;
  8. };
  9. 1var makeNode = function(data, parent) {
  10. 122 return {
  11. data : data,
  12. red : true,
  13. left : null,
  14. right : null
  15. }
  16. };
  17. 1var insert = function(root, data, compare) {
  18. 503 if (root == null) {
  19. 122 return makeNode(data, null);
  20. } else {
  21. 381 var cmp = compare(data, root.data);
  22. 381 if (cmp) {
  23. 381 var dir = cmp == -1 ? "left" : "right";
  24. 381 var otherDir = dir == "left" ? "right" : "left";
  25. 381 root[dir] = insert(root[dir], data, compare);
  26. 381 var node = root[dir];
  27. 381 if (isRed(node)) {
  28. 294 var sibling = root[otherDir];
  29. 294 if (isRed(sibling)) {
  30. /* Case 1 */
  31. 68 root.red = true;
  32. 68 node.red = false;
  33. 68 sibling.red = false;
  34. } else {
  35. 226 if (isRed(node[dir])) {
  36. 43 root = rotateSingle(root, otherDir);
  37. 183 } else if (isRed(node[otherDir])) {
  38. 11 root = rotateDouble(root, otherDir);
  39. }
  40. }
  41. }
  42. }
  43. }
  44. 381 return root;
  45. };
  46. 1var rotateSingle = function(root, dir) {
  47. 70 var otherDir = dir == "left" ? "right" : "left";
  48. 70 var save = root[otherDir];
  49. 70 root[otherDir] = save[dir];
  50. 70 save[dir] = root;
  51. 70 root.red = true;
  52. 70 save.red = false;
  53. 70 return save;
  54. };
  55. 1var rotateDouble = function(root, dir) {
  56. 12 var otherDir = dir == "left" ? "right" : "left";
  57. 12 root[otherDir] = rotateSingle(root[otherDir], otherDir);
  58. 12 return rotateSingle(root, dir);
  59. };
  60. 1var remove = function (root, data, done, compare) {
  61. 52 if (root == null) {
  62. 2 done.done = true;
  63. } else {
  64. 50 var dir;
  65. 50 if (compare(data, root.data) == 0) {
  66. 22 if (root.left == null || root.right == null) {
  67. 16 var save = root[root.left == null ? "right" : "left"];
  68. /* Case 0 */
  69. 16 if (isRed(root)) {
  70. 2 done.done = true;
  71. 14 } else if (isRed(save)) {
  72. 5 save.red = false;
  73. 5 done.done = true;
  74. }
  75. 16 return save;
  76. }
  77. else {
  78. 6 var heir = root.right, p;
  79. 6 while (heir.left != null) {
  80. 2 p = heir;
  81. 2 heir = heir.left;
  82. }
  83. 6 p && (p.left = null);
  84. 6 root.data = heir.data;
  85. 6 data = heir.data;
  86. }
  87. }
  88. 34 dir = compare(data, root.data) == -1 ? "left" : "right";
  89. 34 root[dir] = remove(root[dir], data, done, compare);
  90. 34 !done.done && (root = removeBalance(root, dir, done));
  91. }
  92. 36 return root;
  93. };
  94. 1var removeBalance = function(root, dir, done) {
  95. 11 var notDir = dir == "left" ? "right" : "left";
  96. 11 var p = root, s = p[notDir];
  97. 11 if (isRed(s)) {
  98. 2 root = rotateSingle(root, dir);
  99. 2 s = p[notDir];
  100. }
  101. 11 if (s != null) {
  102. 11 if (!isRed(s.left) && !isRed(s.right)) {
  103. 9 isRed(p) && (done.done = true);
  104. 9 p.red = 0;
  105. 9 s.red = 1;
  106. } else {
  107. 2 var save = p.red, newRoot = ( root === p );
  108. 2 p = (isRed(s[notDir]) ? rotateSingle : rotateDouble)(p, dir);
  109. 2 p.red = save;
  110. 2 p.left.red = p.right.red = 0;
  111. 2 if (newRoot) {
  112. 1 root = p;
  113. } else {
  114. 1 root[dir] = p;
  115. }
  116. 2 done.done = true;
  117. }
  118. }
  119. 11 return root;
  120. };
  121. 1var RedBlackTree;
  122. /**
  123. * @class <p>A RedBlack tree is a form of a self balancing binary tree.</p>
  124. *
  125. * <b>Performance</b>
  126. * <table>
  127. * <tr><td></td><td>Best</td><td>Worst</td></tr>
  128. * <tr><td>Space</td><td>O(n)</td><td>O(n)</td></tr>
  129. * <tr><td>Search</td><td>O(log n)</td><td>O(log n)</td></tr>
  130. * <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
  131. * <tr><td>Delete</td><td>O(log n)</td><td>O(log n)</td></tr>
  132. * <table>
  133. * @name RedBlackTree
  134. * @augments comb.collections.Tree
  135. * @memberOf comb.collections
  136. * @ignoreCode
  137. */
  138. 1module.exports = exports = define(Tree, {
  139. instance : {
  140. /**@lends comb.collections.RedBlackTree.prototype*/
  141. insert : function(data) {
  142. 122 this.__root = insert(this.__root, data, this.compare);
  143. 122 this.__root.red = false;
  144. },
  145. remove : function(data) {
  146. 18 var done = {done : false};
  147. 18 var root = remove(this.__root, data, done, this.compare);
  148. 18 if (root != null)
  149. 17 root.red = 0;
  150. 18 this.__root = root;
  151. },
  152. __printNode : function(node, level) {
  153. 37 var str = [];
  154. 37 if (node == null || node == undefined) {
  155. 19 str.push(multiply('\t', level));
  156. 19 str.push("~");
  157. 19 console.log(str.join(""));
  158. } else {
  159. 18 this.__printNode(node.right, level + 1);
  160. 18 str.push(multiply('\t', level));
  161. 18 str.push((node.red ? "RED" : "BLACK") + ":" + node.data + "\n");
  162. 18 console.log(str.join(""));
  163. 18 this.__printNode(node.left, level + 1);
  164. }
  165. }
  166. }
  167. });
collections/Stack.js
Coverage100.00 SLOC117 LOC30 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. base = require("../base");
  4. /**
  5. * @class <p>LIFO Data structure</p>
  6. * @name Stack
  7. * @augments comb.collections.Collection
  8. * @memberOf comb.collections
  9. *
  10. * @property {Number} count the current number of elements in this queue
  11. * @property {Boolean} isEmpty true if this queue is empty
  12. * @property {Array} values a copy of the values contained in this queue
  13. * @ignoreCode
  14. */
  15. 1module.exports = exports = define(Collection, {
  16. instance : {
  17. /**@lends comb.collections.Stack.prototype*/
  18. constructor : function() {
  19. 1 this.__stack = [];
  20. 1 this.__next = -1;
  21. },
  22. /**
  23. * Add an item to the tail of this stack
  24. * @param {*} data item to qppend to this stack
  25. *
  26. */
  27. push : function(data) {
  28. 23 this.__stack[++this.__next] = data;
  29. },
  30. /**
  31. * Removes the tail of this static
  32. * @return {*} the data at the tail of this stack
  33. */
  34. pop : function() {
  35. 11 var ret = undefined, stack, next = this.__next;
  36. 11 if (next >= 0) {
  37. 10 stack = this.__stack;
  38. 10 ret = stack[next];
  39. 10 stack[this.__next--] = undefined;
  40. }
  41. 11 return ret;
  42. },
  43. /**
  44. * Retrieves the item at the tail of the stack without removing it
  45. *
  46. * @return {*} The element at the tail of the stack. Returns undefined if the stack is empty.
  47. */
  48. peek : function() {
  49. 1 var ret = undefined,next = this.__next;
  50. 1 if (next >= 0) {
  51. 1 ret = this.__stack[next];
  52. }
  53. 1 return ret;
  54. },
  55. /**
  56. * Removes all items from this stack.
  57. */
  58. clear : function() {
  59. 1 this.__stack.length = 0;
  60. 1 this.__next = -1;
  61. },
  62. /**
  63. * Determine if this stack contains the element
  64. * @param {*} obj the object to find
  65. *
  66. * @return {Boolean} true if this stack contains the element
  67. */
  68. contains : function(obj) {
  69. 3 return this.__stack.indexOf(obj) != -1;
  70. },
  71. /**
  72. * Removes an element from this stack.
  73. * @param {*} obj the data to remove.
  74. *
  75. * @return {Boolean} true if the element was removed, false otherwise.
  76. */
  77. remove : function(obj) {
  78. 7 var index = this.__stack.indexOf(obj), ret = false;
  79. 7 if (index != -1) {
  80. 7 if (index == this.__next) {
  81. 1 this.pop();
  82. } else {
  83. 6 this.__stack.splice(index, 1);
  84. 6 this.__next--;
  85. }
  86. 7 ret = true;
  87. }
  88. 7 return ret;
  89. },
  90. toString : function(){
  91. 3 return this.__stack.toString();
  92. },
  93. getters : {
  94. count : function() {
  95. 3 return this.__next + 1;
  96. },
  97. isEmpty : function() {
  98. 5 return this.__next < 0;
  99. },
  100. values : function() {
  101. 1 return this.__stack.slice(0, this.__next + 1).reverse();
  102. }
  103. }
  104. }
  105. });
collections/Tree.js
Coverage100.00 SLOC457 LOC160 Missed0
  1. 1var define = require("../define").define,
  2. Collection = require("./Collection"),
  3. Iterable = require("./Iterable"),
  4. base = require("../base"),
  5. multiply = base.string.multiply;
  6. 1var compare = function(a, b) {
  7. 2757 var ret = 0;
  8. 2757 if (a > b) {
  9. 1571 return 1;
  10. 1186 } else if (a < b) {
  11. 936 return -1;
  12. 250 }else if(!b){
  13. 1 return 1;
  14. }
  15. 249 return ret;
  16. };
  17. 1var Tree = define([Collection, Iterable], {
  18. instance : {
  19. /**@lends comb.collections.Tree.prototype*/
  20. /**
  21. * Prints a node
  22. * @param node node to print
  23. * @param level the current level the node is at, Used for formatting
  24. */
  25. __printNode : function(node, level) {
  26. //console.log(level);
  27. 37 var str = [];
  28. 37 if (node == null || node == undefined) {
  29. 19 str.push(multiply('\t', level));
  30. 19 str.push("~");
  31. 19 console.log(str.join(""));
  32. } else {
  33. 18 this.__printNode(node.right, level + 1);
  34. 18 str.push(multiply('\t', level));
  35. 18 str.push(node.data + "\n");
  36. 18 console.log(str.join(""));
  37. 18 this.__printNode(node.left, level + 1);
  38. }
  39. },
  40. /**
  41. * Base Class for all tree implementations
  42. * @constructs
  43. * @augments comb.collections.Collection
  44. * @augments comb.collections.Iterable
  45. * @memberOf comb.collections
  46. *
  47. * @param {Object} options options to initialize the tree
  48. * @param {Function} options.compare function used to compare items in a tree must return an integer
  49. * <ul>
  50. * </li>-1 for less than</li>
  51. * </li>0 for equal</li>
  52. * </li>1 for greater than</li>
  53. * </ul>
  54. *
  55. */
  56. constructor : function(options) {
  57. 45 options = options || {};
  58. 45 this.compare = options.compare || compare;
  59. 45 this.__root = null;
  60. },
  61. /**
  62. * Inserts an item into the tree
  63. * @param {Anything} data the item to insert
  64. */
  65. insert : function(data) {
  66. 1 throw new Error("Not Implemented");
  67. },
  68. /**
  69. * Removes an item from the tree
  70. * @param {Anything} data the item to insert
  71. */
  72. remove : function(data) {
  73. 1 throw new Error("Not Implemented");
  74. },
  75. /**
  76. * Clear all items from a tree
  77. */
  78. clear : function() {
  79. 4 this.__root = null;
  80. },
  81. /**
  82. * Test if a tree is empty
  83. *
  84. * @return {Boolean} true if empty false otherwise
  85. */
  86. isEmpty : function() {
  87. 15 return this.__root == null;
  88. },
  89. /**
  90. * Traverse a tree until the callback function returns false
  91. *
  92. * <p><b>Not typically used directly</b></p>
  93. *
  94. * @param {Object} node the node to start at
  95. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  96. * @param {Function} callback called for each item, traversal continues until the function returns false
  97. *
  98. */
  99. traverseWithCondition : function(node, order, callback) {
  100. 2172 var cont = true;
  101. 2172 if (node) {
  102. 1270 order = order || Tree.PRE_ORDER;
  103. 1270 if (order === Tree.PRE_ORDER) {
  104. 152 cont = callback(node.data);
  105. 152 if (cont) {
  106. 144 cont = this.traverseWithCondition(node.left, order, callback);
  107. 144 cont && (cont = this.traverseWithCondition(node.right, order, callback));
  108. }
  109. 1118 } else if (order === Tree.IN_ORDER) {
  110. 510 cont = this.traverseWithCondition(node.left, order, callback);
  111. 510 if (cont) {
  112. 444 cont = callback(node.data);
  113. 444 cont && (cont = this.traverseWithCondition(node.right, order, callback));
  114. }
  115. 608 } else if (order === Tree.POST_ORDER) {
  116. 178 cont = this.traverseWithCondition(node.left, order, callback);
  117. 178 if (cont) {
  118. 158 cont && (cont = this.traverseWithCondition(node.right, order, callback));
  119. 158 cont && (cont = callback(node.data));
  120. }
  121. 430 } else if (order === Tree.REVERSE_ORDER) {
  122. 430 cont = this.traverseWithCondition(node.right, order, callback);
  123. 430 if (cont) {
  124. 384 cont = callback(node.data);
  125. 384 cont && (cont = this.traverseWithCondition(node.left, order, callback));
  126. }
  127. }
  128. }
  129. 2172 return cont;
  130. },
  131. /**
  132. * Traverse a tree
  133. *
  134. * <p><b>Not typically used directly</b></p>
  135. *
  136. * @param {Object} node the node to start at
  137. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  138. * @param {Function} callback called for each item
  139. *
  140. */
  141. traverse : function(node, order, callback) {
  142. 3449 if (node) {
  143. 1928 order = order || Tree.PRE_ORDER;
  144. 1928 if (order === Tree.PRE_ORDER) {
  145. 632 callback(node.data);
  146. 632 this.traverse(node.left, order, callback);
  147. 632 this.traverse(node.right, order, callback);
  148. 1296 } else if (order === Tree.IN_ORDER) {
  149. 632 this.traverse(node.left, order, callback);
  150. 632 callback(node.data);
  151. 632 this.traverse(node.right, order, callback);
  152. 664 } else if (order === Tree.POST_ORDER) {
  153. 632 this.traverse(node.left, order, callback);
  154. 632 this.traverse(node.right, order, callback);
  155. 632 callback(node.data);
  156. 32 } else if (order === Tree.REVERSE_ORDER) {
  157. 32 this.traverseWithCondition(node.right, order, callback);
  158. 32 callback(node.data);
  159. 32 this.traverseWithCondition(node.left, order, callback);
  160. }
  161. }
  162. },
  163. /**
  164. * Loop through each item in the tree
  165. * @param {Function} cb called for each item in the tree
  166. * @param {Object} [scope=this] scope to call the function in
  167. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  168. */
  169. forEach : function(cb, scope, order) {
  170. 28 if (typeof cb !== "function")
  171. 4 throw new TypeError();
  172. 24 order = order || Tree.IN_ORDER;
  173. 24 scope = scope || this;
  174. 24 this.traverse(this.__root, order, function(node) {
  175. 312 cb.call(scope, node, this);
  176. });
  177. },
  178. /**
  179. * Loop through each item in the tree, collecting the value returned by the callback funciton.
  180. * @param {Function} cb called for each item in the tree.
  181. * Whatever the function returns is inserted into the return tree
  182. * @param {Object} [scope=this] scope to call the function in
  183. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  184. *
  185. * @return {comb.collections.Tree} the tree with the mapped items
  186. */
  187. map : function(cb, scope, order) {
  188. 28 if (typeof cb !== "function")
  189. 4 throw new TypeError();
  190. 24 order = order || Tree.IN_ORDER;
  191. 24 scope = scope || this;
  192. 24 var construct = this._static;
  193. 24 var ret = new this._static();
  194. 24 this.traverse(this.__root, order, function(node) {
  195. 312 ret.insert(cb.call(scope, node, this));
  196. });
  197. 24 return ret;
  198. },
  199. /**
  200. * Filters a tree, only returning items that result in true being returned from the callback
  201. *
  202. * @param {Function} cb called for each item in the tree
  203. * @param {Object} [scope=this] scope to call the function in
  204. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  205. *
  206. * @return {comb.collections.Tree} the tree with items that resulted in true being returned from the callback
  207. */
  208. filter : function(cb, scope, order) {
  209. 16 if (typeof cb !== "function")
  210. 4 throw new TypeError();
  211. 12 order = order || Tree.IN_ORDER;
  212. 12 scope = scope || this;
  213. 12 var ret = new this._static();
  214. 12 this.traverse(this.__root, order, function(node) {
  215. 216 var include = cb.call(scope, node, this);
  216. 216 include && ret.insert(node);
  217. });
  218. 12 return ret;
  219. },
  220. /**
  221. * Reduces a tree
  222. *
  223. * @param {Function} fun called for each item in the tree
  224. * @param [accumulator=First item in tree(Order dependant)] scope to call the function in
  225. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  226. *
  227. * @return the result of the reduce function
  228. */
  229. reduce : function(fun, accumulator, order) {
  230. 36 var arr = this.toArray(order);
  231. 36 var args = [fun];
  232. 36 !base.isUndefinedOrNull(accumulator) && args.push(accumulator)
  233. 36 return arr.reduce.apply(arr, args);
  234. },
  235. /**
  236. * Reduces from right to left
  237. *
  238. * @param {Function} fun called for each item in the tree
  239. * @param [accumulator=First item in tree(Order dependant)] scope to call the function in
  240. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  241. *
  242. * @return the result of the reduce function
  243. */
  244. reduceRight : function(fun, accumulator, order) {
  245. 12 var arr = this.toArray(order);
  246. 12 var args = [fun];
  247. 12 !base.isUndefinedOrNull(accumulator) && args.push(accumulator)
  248. 12 return arr.reduceRight.apply(arr, args);
  249. },
  250. /**
  251. * Determines if every item meets the condition returned by the callback.
  252. *
  253. * @param {Function} cb called for each item in the tree
  254. * @param {Object} [scope=this] scope to call the function in
  255. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  256. *
  257. * @return {Boolean} True if every item passed false otherwise
  258. */
  259. every : function(cb, scope, order) {
  260. 36 if (typeof cb !== "function")
  261. 4 throw new TypeError();
  262. 32 order = order || Tree.IN_ORDER;
  263. 32 scope = scope || this;
  264. 32 var ret = false;
  265. 32 this.traverseWithCondition(this.__root, order, function(node) {
  266. 264 return (ret = cb.call(scope, node, this));
  267. });
  268. 32 return ret;
  269. },
  270. /**
  271. * Determines if some item meet the condition returned by the callback. Traversal ends the first time true is found.
  272. *
  273. * @param {Function} cb called for each item in the tree
  274. * @param {Object} [scope=this] scope to call the function in
  275. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  276. *
  277. * @return {Boolean} True if every item passed false otherwise
  278. */
  279. some : function(cb, scope, order) {
  280. 36 if (typeof cb !== "function")
  281. 4 throw new TypeError();
  282. 32 order = order || Tree.IN_ORDER;
  283. 32 scope = scope || this;
  284. 32 var ret;
  285. 32 this.traverseWithCondition(this.__root, order, function(node) {
  286. 284 ret = cb.call(scope, node, this);
  287. 284 return !ret;
  288. });
  289. 32 return ret;
  290. },
  291. /**
  292. * Converts a tree into an array based on the specified order
  293. *
  294. * @param {Tree.PRE_ORDER|Tree.POST_ORDER|Tree.IN_ORDER|Tree.REVERSE_ORDER} [order=Tree.IN_ORDER] the traversal scheme
  295. *
  296. * @return {Array} array of all items in the order specified.
  297. */
  298. toArray : function(order) {
  299. 72 order = order || Tree.IN_ORDER;
  300. 72 var arr = [];
  301. 72 this.traverse(this.__root, order, function(node) {
  302. 1056 arr.push(node);
  303. });
  304. 72 return arr;
  305. },
  306. /**
  307. * Determines if a value is contained in the tree
  308. * @param {*} value the value to find
  309. *
  310. * @return {Boolean} true if the tree contains the item false otherwise.
  311. */
  312. contains : function(value) {
  313. 160 var ret = false;
  314. 160 var root = this.__root;
  315. 160 while (root != null) {
  316. 376 var cmp = this.compare(value, root.data);
  317. 376 if (cmp) {
  318. 271 root = root[(cmp == -1) ? "left" : "right"];
  319. } else {
  320. 105 ret = true;
  321. 105 root = null;
  322. }
  323. }
  324. 160 return ret;
  325. },
  326. /**
  327. * Finds a value in the tree
  328. * @param {*} value the value to find
  329. *
  330. * @return the value of the node that matched
  331. */
  332. find : function(value) {
  333. 8 var ret;
  334. 8 var root = this.__root;
  335. 8 while (root != null) {
  336. 35 var cmp = this.compare(value, root.data);
  337. 35 if (cmp) {
  338. 31 root = root[(cmp == -1) ? "left" : "right"];
  339. } else {
  340. 4 ret = root.data;
  341. 4 break;
  342. }
  343. }
  344. 8 return ret;
  345. },
  346. /**
  347. * Find all values less than a value
  348. * @param {*} value the value to find nodes less than
  349. * @param {Boolean} [exclusive=false] if true the value will NOT be included in the return array
  350. *
  351. * @return {Array} the array containing all values less than
  352. */
  353. findLessThan : function(value, exclusive) {
  354. //find a better way!!!!
  355. 32 var ret = [], compare = this.compare;
  356. 32 this.traverseWithCondition(this.__root, exports.IN_ORDER, function(v) {
  357. 200 var cmp = compare(value, v);
  358. 200 if ((!exclusive && cmp == 0) || cmp == 1) {
  359. 168 ret.push(v);
  360. 168 return true;
  361. } else {
  362. 32 return false;
  363. }
  364. });
  365. 32 return ret;
  366. },
  367. /**
  368. * Find all greater than a value
  369. * @param {*} value the value to find nodes greater than
  370. * @param {Boolean} [exclusive=false] if true the value will NOT be included in the return array
  371. *
  372. * @return {Array} the array containing all values greater than
  373. */
  374. findGreaterThan : function(value, exclusive) {
  375. //find a better way!!!!
  376. 32 var ret = [], compare = this.compare;
  377. 32 this.traverse(this.__root, exports.REVERSE_ORDER, function(v) {
  378. 416 var cmp = compare(value, v);
  379. 416 if ((!exclusive && cmp == 0) || cmp == -1) {
  380. 372 ret.push(v);
  381. 372 return true;
  382. } else {
  383. 44 return false;
  384. }
  385. });
  386. 32 return ret;
  387. },
  388. /**
  389. * Prints a tree to the console.
  390. */
  391. print : function() {
  392. 4 this.__printNode(this.__root, 0);
  393. }
  394. },
  395. static : {
  396. /** @lends comb.collections.Tree */
  397. /**
  398. * Pre Order
  399. */
  400. PRE_ORDER : "pre_order",
  401. /**
  402. * In Order
  403. */
  404. IN_ORDER : "in_order",
  405. /**
  406. * Post Order
  407. */
  408. POST_ORDER:"post_order",
  409. /**
  410. * Reverse Order
  411. */
  412. REVERSE_ORDER : "reverse_order"
  413. }
  414. });
  415. /**@ignore*/
  416. 1module.exports = exports = Tree;
collections/index.js
Coverage100.00 SLOC21 LOC2 Missed0
  1. 1var comb = exports;
  2. /**
  3. * @ignore
  4. * @namespace Various collections*/
  5. 1comb.collections = {
  6. Collection : require("./Collection"),
  7. Iterable : require("./Iterable"),
  8. Tree : require("./Tree"),
  9. BinaryTree : require("./BinaryTree"),
  10. RedBlackTree : require("./RedBlackTree"),
  11. AnderssonTree : require("./AnderssonTree"),
  12. AVLTree : require("./AVLTree"),
  13. HashTable : require("./HashTable"),
  14. Queue : require("./Queue"),
  15. Stack : require("./Stack"),
  16. Heap : require("./Heap"),
  17. MinHeap : require("./MinHeap"),
  18. MaxHeap : require("./MaxHeap"),
  19. PriorityQueue : require("./PriorityQueue"),
  20. Pool : require("./Pool")
  21. };
define.js
Coverage100.00 SLOC505 LOC138 Missed0
  1. /**
  2. * Used to keep track of classes and to create unique ids
  3. * @ignore
  4. */
  5. 1var classCounter = 0;
  6. 1var callSuper = function (args, a) {
  7. 1121 var meta = this.__meta,
  8. supers = meta.supers,
  9. l = supers.length, superMeta = meta.superMeta, pos = superMeta.pos;
  10. 1121 if (l > pos) {
  11. 976 a && (args = a);
  12. 976 var name = superMeta.name, f = superMeta.f, m;
  13. 976 do {
  14. 976 m = supers[pos][name];
  15. 976 if ("function" === typeof m && (m = m._f || m) !== f) {
  16. 976 superMeta.pos = 1 + pos;
  17. 976 return m.apply(this, args);
  18. }
  19. } while (l > ++pos);
  20. }
  21. 145 return null;
  22. };
  23. 1var defaultFunction = function () {
  24. 140 var meta = this.__meta || {},
  25. supers = meta.supers,
  26. l = supers.length, superMeta = meta.superMeta, pos = superMeta.pos;
  27. 140 if (l > pos) {
  28. 123 var name = superMeta.name, f = superMeta.f, m;
  29. 123 do {
  30. 173 m = supers[pos][name];
  31. 173 if ("function" === typeof m && (m = m._f || m) !== f) {
  32. 94 superMeta.pos = 1 + pos;
  33. 94 return m.apply(this, arguments);
  34. }
  35. } while (l > ++pos);
  36. }
  37. 46 return null;
  38. };
  39. 1var functionWrapper = function (f, name) {
  40. 306 var wrapper = function () {
  41. 13911 var ret, meta = this.__meta || {};
  42. 13911 var orig = meta.superMeta;
  43. 13911 meta.superMeta = {f:f, pos:0, name:name};
  44. 13911 ret = f.apply(this, arguments);
  45. 13862 meta.superMeta = orig;
  46. 13862 return ret;
  47. };
  48. 306 wrapper._f = f;
  49. 306 return wrapper;
  50. };
  51. /**
  52. * @ignore
  53. */
  54. 1var defineMixinProps = function (child, proto) {
  55. 34 var operations = proto.setters || {};
  56. 34 for (var i in operations) {
  57. 15 if (!child.__lookupSetter__(i)) { //make sure that the setter isnt already there
  58. 15 child.__defineSetter__(i, operations[i]);
  59. }
  60. }
  61. 34 operations = proto.getters || {};
  62. 34 for (i in operations) {
  63. 15 if (!child.__lookupGetter__(i)) {
  64. //define the getter if the child does not already have it
  65. 15 child.__defineGetter__(i, operations[i]);
  66. }
  67. }
  68. 34 for (var j in proto) {
  69. 103 if (j != "getters" && j != "setters") {
  70. 83 var p = proto[j];
  71. 83 if ("function" === typeof p) {
  72. 78 if (!child.hasOwnProperty(j)) {
  73. 10 child[j] = functionWrapper(defaultFunction, j);
  74. }
  75. } else {
  76. 5 child[j] = p;
  77. }
  78. }
  79. }
  80. };
  81. /**
  82. * @ignore
  83. */
  84. 1var mixin = function () {
  85. 7 var args = Array.prototype.slice.call(arguments), l = args.length;
  86. 7 var child = this.prototype, childMeta = child.__meta, thisMeta = this.__meta, bases = child.__meta.bases, staticBases = bases.slice(),
  87. staticSupers = thisMeta.supers || [], supers = childMeta.supers|| [];
  88. 7 for (var i = 0; i < l; i++) {
  89. 17 var m = args[i], mProto = m.prototype;
  90. 17 var protoMeta = mProto.__meta, meta = m.__meta;
  91. 17 !protoMeta && (protoMeta = (mProto.__meta = {proto :mProto || {}}));
  92. 17 !meta && (meta = (m.__meta = {proto :m.__proto__ || {}}));
  93. 17 defineMixinProps(child, protoMeta.proto || {});
  94. 17 defineMixinProps(this, meta.proto || {});
  95. //copy the bases for static,
  96. 17 mixinSupers(m.prototype, supers, bases);
  97. 17 mixinSupers(m, staticSupers, staticBases);
  98. }
  99. 7 return this;
  100. };
  101. /**
  102. * @ignore
  103. */
  104. 1var mixinSupers = function (sup, arr, bases) {
  105. 214 var meta = sup.__meta;
  106. 214 !meta && (meta = (sup.__meta = {}));
  107. 214 var unique = sup.__meta.unique;
  108. 214 !unique && (meta.unique = "define" + ++classCounter);
  109. //check it we already have this super mixed into our prototype chain
  110. //if true then we have already looped their supers!
  111. 214 if (bases.indexOf(unique) == -1) {
  112. //add their id to our bases
  113. 138 bases.push(unique);
  114. 138 var supers = sup.__meta.supers || [], i = supers.length - 1 || 0;
  115. 138 while (i >= 0) {
  116. 118 mixinSupers(supers[i--], arr, bases);
  117. }
  118. 138 arr.unshift(sup);
  119. }
  120. };
  121. /**
  122. * @ignore
  123. */
  124. 1var defineProps = function (child, proto) {
  125. 88 var operations = proto.setters;
  126. 88 if (operations) {
  127. 8 for (var i in operations) {
  128. 13 child.__defineSetter__(i, operations[i]);
  129. }
  130. }
  131. 88 operations = proto.getters || {};
  132. 88 if (operations) {
  133. 88 for (i in operations) {
  134. 47 child.__defineGetter__(i, operations[i]);
  135. }
  136. }
  137. 88 for (i in proto) {
  138. 347 if (i != "getters" && i != "setters") {
  139. 326 var f = proto[i];
  140. 326 if ("function" === typeof f) {
  141. 293 var meta = f.__meta || {};
  142. 293 if (!meta.isConstructor) {
  143. 292 child[i] = functionWrapper(f, i);
  144. } else {
  145. 1 child[i] = f;
  146. }
  147. } else {
  148. 33 child[i] = f;
  149. }
  150. }
  151. }
  152. };
  153. 1var _export = function (obj, name) {
  154. 11 if (obj && name) {
  155. 2 obj[name] = this;
  156. } else {
  157. 9 obj.exports = obj = this;
  158. }
  159. 11 return this;
  160. };
  161. /**
  162. * @ignore
  163. */
  164. 1var __define = function (child, sup, proto) {
  165. 46 var childProto = child.prototype, supers = [];
  166. 46 var unique = "define" + ++classCounter, bases = [], staticBases = [];
  167. 46 var instanceSupers = [], staticSupers = [];
  168. 46 var meta = childProto.__meta = {
  169. supers:instanceSupers,
  170. unique:unique,
  171. bases:bases,
  172. superMeta:{
  173. f:null,
  174. pos:0,
  175. name:null
  176. }
  177. };
  178. 46 var childMeta = child.__meta = {
  179. supers:staticSupers,
  180. unique:unique,
  181. bases:staticBases,
  182. isConstructor:true,
  183. superMeta:{
  184. f:null,
  185. pos:0,
  186. name:null
  187. }
  188. };
  189. 46 if (sup) {
  190. 31 supers = Array.isArray(sup) ? sup : [sup];
  191. 31 sup = supers.shift();
  192. 31 child.__proto__ = sup;
  193. 31 childProto.__proto__ = sup.prototype;
  194. 31 mixinSupers(sup.prototype, instanceSupers, bases),
  195. mixinSupers(sup, staticSupers, staticBases);
  196. }
  197. 46 if (proto) {
  198. 44 var instance = meta.proto = proto.instance || {};
  199. 44 !instance.hasOwnProperty("constructor") && (instance.constructor = defaultFunction);
  200. 44 var stat = childMeta.proto = proto.static || {};
  201. 44 stat.init = stat.init || defaultFunction;
  202. 44 defineProps(childProto, instance, false);
  203. 44 defineProps(child, stat, true);
  204. } else {
  205. 2 meta.proto = {};
  206. 2 childMeta.proto = {};
  207. 2 child.init = functionWrapper(defaultFunction, "init");
  208. 2 childProto.constructor = functionWrapper(defaultFunction, "constructor");
  209. }
  210. 46 if (supers.length) {
  211. 7 mixin.apply(child, supers);
  212. }
  213. 46 child.mixin = mixin;
  214. 46 childProto._super = child._super = callSuper;
  215. 46 child.as = _export;
  216. 46 childProto._static = child;
  217. };
  218. /**
  219. * Defines a new class to be used
  220. *
  221. * <p>
  222. * Class methods
  223. * <ul>
  224. * <li>as(module | object, name): exports the object to module or the object with the name</li>
  225. * <li>mixin(mixin) : mixes in an object</li>
  226. * </ul>
  227. * </br>
  228. * Instance methods
  229. * <ul>
  230. * <li>_super(argumnents, [?newargs]): calls the super of the current method</li>
  231. * </ul>
  232. *
  233. * </br>
  234. * Instance properties
  235. * <ul>
  236. * <li>_static: use to reference class properties and methods</li>
  237. * </ul>
  238. *
  239. * </p>
  240. *
  241. *
  242. * @example
  243. * //Class without a super class
  244. * var Mammal = comb.define(null, {
  245. * instance : {
  246. *
  247. * constructor: function(options) {
  248. * options = options || {};
  249. * this._super(arguments);
  250. * this._type = options.type || "mammal";
  251. * },
  252. *
  253. * speak : function() {
  254. * return "A mammal of type " + this._type + " sounds like";
  255. * },
  256. *
  257. * //Define your getters
  258. * getters : {
  259. * type : function() {
  260. * return this._type;
  261. * }
  262. * },
  263. *
  264. * //Define your setters
  265. * setters : {
  266. * type : function(t) {
  267. * this._type = t;
  268. * }
  269. * }
  270. * },
  271. *
  272. * //Define your static methods
  273. * static : {
  274. * soundOff : function() {
  275. * return "Im a mammal!!";
  276. * }
  277. * }
  278. * });
  279. *
  280. * //Show singular inheritance
  281. *var Wolf = comb.define(Mammal, {
  282. * instance: {
  283. * constructor: function(options) {
  284. * options = options || {};
  285. * //You can call your super constructor, or you may not
  286. * //call it to prevent the super initializing parameters
  287. * this._super(arguments);
  288. * this._sound = "growl";
  289. * this._color = options.color || "grey";
  290. * },
  291. *
  292. * speak : function() {
  293. * //override my super classes speak
  294. * //Should return "A mammal of type mammal sounds like a growl"
  295. * return this._super(arguments) + " a " + this._sound;
  296. * },
  297. *
  298. * //add new getters for sound and color
  299. * getters : {
  300. *
  301. * color : function() {
  302. * return this._color;
  303. * },
  304. *
  305. * sound : function() {
  306. * return this._sound;
  307. * }
  308. * },
  309. *
  310. * setters : {
  311. *
  312. * //NOTE color is read only except on initialization
  313. *
  314. * sound : function(s) {
  315. * this._sound = s;
  316. * }
  317. * }
  318. *
  319. * },
  320. *
  321. * static : {
  322. * //override my satic soundOff
  323. * soundOff : function() {
  324. * //You can even call super in your statics!!!
  325. * //should return "I'm a mammal!! that growls"
  326. * return this._super(arguments) + " that growls";
  327. * }
  328. * }
  329. *});
  330. *
  331. *
  332. * //Typical hierarchical inheritance
  333. * // Mammal->Wolf->Dog
  334. * var Dog = comb.define(Wolf, {
  335. * instance: {
  336. * constructor: function(options) {
  337. * options = options || {};
  338. * this._super(arguments);
  339. * //override Wolfs initialization of sound to woof.
  340. * this._sound = "woof";
  341. *
  342. * },
  343. *
  344. * speak : function() {
  345. * //Should return "A mammal of type mammal sounds like a growl thats domesticated"
  346. * return this._super(arguments) + " thats domesticated";
  347. * }
  348. * },
  349. *
  350. * static : {
  351. * soundOff : function() {
  352. * //should return "I'm a mammal!! that growls but now barks"
  353. * return this._super(arguments) + " but now barks";
  354. * }
  355. * }
  356. *});
  357. *
  358. *
  359. *
  360. * dog instanceof Wolf => true
  361. * dog instanceof Mammal => true
  362. * dog.speak() => "A mammal of type mammal sounds like a woof thats domesticated"
  363. * dog.type => "mammal"
  364. * dog.color => "gold"
  365. * dog.sound => "woof"
  366. * Dog.soundOff() => "Im a mammal!! that growls but now barks"
  367. *
  368. * // Mammal->Wolf->Dog->Breed
  369. *var Breed = comb.define(Dog, {
  370. * instance: {
  371. *
  372. * //initialize outside of constructor
  373. * _pitch : "high",
  374. *
  375. * constructor: function(options) {
  376. * options = options || {};
  377. * this._super(arguments);
  378. * this.breed = options.breed || "lab";
  379. * },
  380. *
  381. * speak : function() {
  382. * //Should return "A mammal of type mammal sounds like a
  383. * //growl thats domesticated with a high pitch!"
  384. * return this._super(arguments) + " with a " + this._pitch + " pitch!";
  385. * },
  386. *
  387. * getters : {
  388. * pitch : function() {
  389. * return this._pitch;
  390. * }
  391. * }
  392. * },
  393. *
  394. * static : {
  395. * soundOff : function() {
  396. * //should return "I'M A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
  397. * return this._super(arguments).toUpperCase() + "!";
  398. * }
  399. * }
  400. * });
  401. *
  402. *
  403. * var breed = new Breed({color : "gold", type : "lab"}),
  404. *
  405. *
  406. *
  407. * breed instanceof Dog => true
  408. * breed instanceof Wolf => true
  409. * breed instanceof Mammal => true
  410. * breed.speak() => "A mammal of type lab sounds like a woof "
  411. * + "thats domesticated with a high pitch!"
  412. * breed.type => "lab"
  413. * breed.color => "gold"
  414. * breed.sound => "woof"
  415. * breed.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
  416. *
  417. *
  418. * //Example of multiple inheritance
  419. * //NOTE proto is optional
  420. *
  421. * //Mammal is super class
  422. * //Wolf Dog and Breed inject functionality into the prototype
  423. * var Lab = comb.define([Mammal, Wolf, Dog, Breed]);
  424. *
  425. * var lab = new Lab();
  426. * lab instanceof Wolf => false
  427. * lab instanceof Dog => false
  428. * lab instanceof Breed => false
  429. * lab instanceof Mammal => true
  430. * lab.speak() => "A mammal of type mammal sounds like a"
  431. * + " woof thats domesticated with a high pitch!"
  432. * Lab.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
  433. *
  434. * @name define
  435. * @memberOf comb
  436. *
  437. * @param {Array|Class} super the supers of this class
  438. * @param {Object} [proto] the object used to define this class
  439. * @param {Object} [proto.instance] the instance methods of the class
  440. * @param {Object} [proto.instance.getters] the getters for the class
  441. * @param {Object} [proto.instance.setters] the setters for the class
  442. * @param {Object} [proto.static] the Class level methods of this class
  443. * @param {Object} [proto.static.getters] static getters for the object
  444. * @param {Object} [proto.static.setters] static setters for the object
  445. *
  446. * @returns {Object} the constructor of the class to be used with new keyword
  447. */
  448. 1exports.define = function (sup, proto) {
  449. 43 var child = function () {
  450. 596 this.constructor.apply(this, arguments);
  451. };
  452. 43 __define(child, sup, proto);
  453. 43 return child.init() || child;
  454. };
  455. /**
  456. * Defines a singleton instance of a Class. See {@link define}
  457. * @example
  458. * var MyLab = comb.singleton([Mammal, Wolf, Dog, Breed]);
  459. * var myLab1 = new MyLab();
  460. * myLab1.type = "collie"
  461. * var myLab2 = new MyLab();
  462. * myLab1 === myLab2 => true
  463. * myLab1.type => "collie"
  464. * myLab2.type => "collie"
  465. *
  466. *
  467. * @name singleton
  468. * @memberOf comb
  469. */
  470. 1exports.singleton = function (sup, proto) {
  471. 3 var retInstance;
  472. 3 var child = function () {
  473. 6 if (!retInstance) {
  474. 2 this.constructor.apply(this, arguments);
  475. 2 retInstance = this;
  476. }
  477. 6 return retInstance;
  478. };
  479. 3 __define(child, sup, proto);
  480. 3 return child.init() || child;
  481. };
index.js
Coverage100.00 SLOC101 LOC7 Missed0
  1. 1var base = require("./base");
  2. /**
  3. * @projectName comb
  4. *
  5. * @github https://github.com/Pollenware/comb
  6. *
  7. * @includeDoc [OO] ../docs-md/define.md
  8. * @includeDoc [Promises] ../docs-md/promise.md
  9. * @includeDoc [Logging] ../docs-md/logging.md
  10. * @includeDoc [Utilities] ../docs-md/utilities.md
  11. * @includeDoc [Test Coverage] [../docs-md/coverage.html]
  12. *
  13. *
  14. * @header
  15. *
  16. * #Comb
  17. *
  18. * ##Overview
  19. *
  20. * Framework for node that provides a one stop shop for frequently needed utilities, including:
  21. *
  22. * * [OO utilties](./define.html)
  23. * * Collections
  24. * * [Logging](./logging.html)
  25. * * [String &amp; date formatting](./utilities)
  26. * * [Flow control](./promise.html)
  27. *
  28. *
  29. * ##Installation
  30. *
  31. * `npm install comb`
  32. *
  33. * ##Highlights
  34. *
  35. * * 100% test coverage!
  36. * * comb([define](./comb.html#.define)|[singleton](./comb.html#.singleton))
  37. * * The backbone of comb.
  38. * * Options for classical inheritance models as well as mixins(pseudo multi-inheritance)
  39. * * You can call this._super from any method. Including statically defined ones!
  40. * * Access to your class level properties within an instance
  41. * * Logging
  42. * * Logger inheritance through name spaces
  43. * * Predefined [level](./comb_logging_Level.html) level definition along with the ability to define your own.
  44. * * Multiple appenders including
  45. * * [FileAppender](./comb_logging_appenders_FileAppender.html) - log it to a file
  46. * * [RollingFileAppender](./comb_logging_appenders_RollingFileAppender.html) - log it to a file up to a customizable size then create a new one.
  47. * * [JSONAppender](./comb_logging_appenders_JSONAppender.html) - write it out as JSON to a file.
  48. * * [ConsoleAppender](./comb_logging_appenders_ConsoleAppender.html)- log it to the console
  49. * * Configurable with [files OR programatically](./comb_logger.html#.configure)
  50. * * Collections
  51. * * [RedBlackTree](./comb_collections_RedBlackTree.html)
  52. * * [AVLTree](./comb_collections_AVLTree.html)
  53. * * [AnderssonTree](./comb_collections_AnderssonTree.html)
  54. * * [BinaryTree](./comb_collections_BinaryTree.html)
  55. * * [HashTable](./comb_collections_HashTable.html)
  56. * * [MaxHeap](./comb_collections_MaxHeap.html)
  57. * * [MinHeap](./comb_collections_MinHeap.html)
  58. * * [Pool](./comb_collections_Pool.html)
  59. * * [PriorityQueue](./comb_collections_PriorityQueue.html)
  60. * * [Queue](./comb_collections_Queue.html)
  61. * * [Stack](./comb_collections_Stack.html)
  62. *
  63. * * [Flow control](./promise.html)
  64. * * [Promises](./comb_Promise.html)
  65. * * [PromiseList](./comb_PromiseList.html)
  66. * * [comb.when](./comb.html#.when)
  67. * * [comb.serial](./comb.html#.serial)
  68. *
  69. * @footer
  70. * ##License
  71. *
  72. * MIT <https://github.com/Pollenware/comb/raw/master/LICENSE>
  73. *
  74. * ##Meta
  75. * * Code: `git clone git://github.com/Pollenware/comb.git`
  76. * * Website: <http://pollenware.com>
  77. * * Twitter: [http://twitter.com/pollenware](http://twitter.com/pollenware) - 877.465.4045
  78. */
  79. /**
  80. * Utilities for javascript, optimized for the server environment.
  81. *
  82. *
  83. * @namespace
  84. */
  85. 1var comb = exports;
  86. 1base.merge(exports, base, require("./define"), require("./promise"), require("./plugins"), require("./collections"), require("./logging"));
  87. 1comb.definePlugin = function (obj) {
  88. 1 if (comb.isHash(obj)) {
  89. 1 comb.deepMerge(comb, obj);
  90. }
  91. 1 return comb;
  92. };
logging/appenders/appender.js
Coverage100.00 SLOC167 LOC30 Missed0
  1. 1var define = require("../../define.js").define, base = require("../../base"), Level = require("../level");
  2. 1var APPENDER_TYPES = {};
  3. /**
  4. * @class Base class for all appenders
  5. *
  6. * @name Appender
  7. * @memberOf comb.logging.appenders
  8. *
  9. * @param {Object} [options] options to assign to this Appender
  10. * @param {String} [options.name="appender"] the name of this Appender. If you want two of the same type of appender
  11. * on a logger it must have a different name.
  12. * @param {String} [options.pattern="[{[yyyy-MM-ddTHH:mm:ss:SSS (z)]timeStamp}] {[- 5]levelName} {[-20]name} - {message}"]
  13. * <p>Available Options for formatting see {@link comb.string.format} for formatting options</p>
  14. * <ul>
  15. * <li>timeStamp - the timestamp of the event being logged</li>
  16. * <li>level - the {@link comb.logging.Level} of the event</li>
  17. * <li>levelName - the name of the level being logged</li>
  18. * <li>name - the name of the logger logging the event</li>
  19. * <li>message - the message being logged</li>
  20. * </ul>
  21. * @param {comb.logging.Level|String} [options.level=comb.logging.Level.INFO] the logging level of this appender
  22. * <p><b>Note:</b> the level can be different from the logger in the case that you want a particular logger
  23. * to only log particular event of a level. For example an appender that only logs errors. BEWARE that if the
  24. * appenders level is lower than the logger is will not receive any messages.</p>
  25. *
  26. * @property {String} name the name of this Appender.
  27. * @property {String} pattern the pattern for this Appender.
  28. * @property {comb.logging.Level} level the level of this Appender.
  29. * @ignoreCode
  30. */
  31. 1define(null, {
  32. instance:{
  33. /**@lends comb.logging.appenders.Appender.prototype*/
  34. constructor:function (options) {
  35. 21 options = options || {};
  36. 21 this.name = options.name || "appender";
  37. 21 this.pattern = options.pattern || "[{[yyyy-MM-ddTHH:mm:ss:SSS (z)]timeStamp}] {[- 5]levelName} {[-20]name} - {message}";
  38. 21 var level = options.level;
  39. 21 if (options.level && (level = Level.toLevel(level))) {
  40. 3 this.__level = level;
  41. }
  42. },
  43. /**
  44. * Appends a message to a log.
  45. * <b>This method is abstract and must be implemented in subclasses</b>
  46. * @param {Object} event the logging event to log.
  47. * @param {Date} event.timeStamp the timeStamp of the event.
  48. * @param {comb.logging.Level} level the level of the event.
  49. * @param {String} name the name of the logger the event was emitted from.
  50. * @param {String} message the message that is being logged.
  51. *
  52. */
  53. append:function (event) {
  54. 1 throw new Error("abstract method");
  55. },
  56. _canAppend:function (event) {
  57. 76 return !base.isUndefinedOrNull(this.__level) && event.level.isGreaterOrEqualToo(this.__level);
  58. },
  59. /**@ignore*/
  60. setters:{
  61. /**@ignore*/
  62. level:function (level) {
  63. 59 if (level && level instanceof Level) {
  64. 52 this.__level = level;
  65. } else {
  66. //try to get the level
  67. 7 level = Level.toLevel(level);
  68. 7 if (level) {
  69. 4 this.__level = level;
  70. }
  71. }
  72. },
  73. pattern:function (patt) {
  74. 22 if (base.isString(patt)) {
  75. 22 this.__pattern = patt;
  76. }
  77. },
  78. name:function (name) {
  79. 27 if (base.isString(name)) {
  80. 27 this.__name = name;
  81. }
  82. }
  83. },
  84. /**@ignore*/
  85. getters:{
  86. /**@ignore*/
  87. level:function () {
  88. 92 return this.__level;
  89. },
  90. name:function () {
  91. 112 return this.__name;
  92. },
  93. pattern:function () {
  94. 3 return this.__pattern;
  95. }
  96. }
  97. },
  98. "static":{
  99. /**@lends comb.logging.appenders.Appender*/
  100. /**
  101. * Register an appender so it can be used with {@link comb.logging.PropertyConfigurator}
  102. *
  103. * @example
  104. *
  105. * var Appender = comb.logging.appenders.Appender;
  106. * comb.define(Appender, {
  107. * instance : {
  108. * append : function(event){
  109. * //log the message
  110. * }
  111. * }
  112. * }).registerType("MyAppender").as(module);
  113. *
  114. * @param {String} type the identifier for your appender type.
  115. * @return returns the Appender class for chaining.
  116. */
  117. registerType:function (type) {
  118. 5 if (base.isString(type)) {
  119. 5 APPENDER_TYPES[type.toLowerCase()] = this;
  120. }
  121. 5 return this;
  122. },
  123. /**
  124. * Acts as a factory for appenders.
  125. *
  126. * @example
  127. *
  128. * var logging = comb.logging,
  129. * Logger = logging.Logger,
  130. * Appender = logging.appenders.Appender;
  131. *
  132. * var logger = comb.logging.Logger.getLogger("my.logger");
  133. * logger.addAppender(Appender.createAppender("consoleAppender"));
  134. *
  135. * @param {String} type the type of appender to create.
  136. * @param {Object} [options={}] additional options to pass to the appender.
  137. * @return {comb.logging.appenders.Appender} an appender to add to a logger.
  138. */
  139. createAppender:function (type, options) {
  140. 14 var caseType = type.toLowerCase();
  141. 14 if (caseType in APPENDER_TYPES) {
  142. 13 return new APPENDER_TYPES[caseType](options);
  143. } else {
  144. 1 throw new Error(type + " appender is not registered!");
  145. }
  146. }
  147. }
  148. }).as(module);
logging/appenders/consoleAppender.js
Coverage100.00 SLOC37 LOC17 Missed0
  1. 1var define = require("../../define.js").define, base = require("../../base"), string = base.string, style = string.style, format = string.format, Appender = require("./appender"), Level = require("../level");
  2. /**
  3. * @class Appends messages to the console.
  4. *
  5. * @name ConsoleAppender
  6. * @augments comb.logging.appenders.Appender
  7. * @memberOf comb.logging.appenders
  8. */
  9. 1define(Appender, {
  10. instance:{
  11. constructor:function (options) {
  12. 9 options = options || {};
  13. 9 !options.name && (options.name = "consoleAppender");
  14. 9 this._super(arguments, [options]);
  15. },
  16. append:function (event) {
  17. 7 if (this._canAppend(event)) {
  18. 7 var message = format(this.__pattern, event);
  19. 7 var level = event.level;
  20. 7 if (Level.ERROR.equals(level) || Level.FATAL.equals(level)) {
  21. 3 console.log(style(message, "red"));
  22. 4 } else if (Level.WARN.equals(level)) {
  23. 1 console.log(style(message, "yellow"));
  24. 3 } else if (Level.DEBUG.equals(level)) {
  25. 1 console.log(style(message, "magenta"));
  26. 2 } else if (Level.TRACE.equals(level)) {
  27. 1 console.log(style(message, "cyan"));
  28. } else {
  29. 1 console.log(message);
  30. }
  31. }
  32. }
  33. }
  34. }).registerType("ConsoleAppender").as(module);
logging/appenders/fileAppender.js
Coverage100.00 SLOC83 LOC22 Missed0
  1. 1var define = require("../../define.js").define,
  2. base = require("../../base"),
  3. promise = require("../../promise"),
  4. string = base.string,
  5. Promise = promise.Promise,
  6. PromiseList = promise.PromiseList,
  7. style = string.style,
  8. format = string.format,
  9. Appender = require("./appender"),
  10. Level = require("../level"),
  11. fs = require("fs");
  12. /**
  13. * @class Appends messages to a file.
  14. *
  15. * @example
  16. * var fileAppender = new comb.logging.appenders.FileAppender({
  17. * file : "/var/log/myLog.log"
  18. * });
  19. *
  20. *
  21. * @name FileAppender
  22. * @augments comb.logging.appenders.Appender
  23. * @memberOf comb.logging.appenders
  24. *
  25. * @param {Object} [options] options to assign to this Appender
  26. * @param {String} [options.name="appender"] the name of this Appender. If you want two of the same type of appender
  27. * on a logger it must have a different name.
  28. * @param {String} [options.pattern="[{[yyyy-MM-ddTHH:mm:ss:SSS (z)]timeStamp}] {[- 5]levelName} {[-20]name} - {message}"]
  29. * <p>Available Options for formatting see {@link comb.string.format} for formatting options</p>
  30. * <ul>
  31. * <li>timeStamp - the timestamp of the event being logged</li>
  32. * <li>level - the {@link comb.logging.Level} of the event</li>
  33. * <li>levelName - the name of the level being logged</li>
  34. * <li>name - the name of the logger logging the event</li>
  35. * <li>message - the message being logged</li>
  36. * </ul>
  37. * @param {comb.logging.Level|String} [options.level=comb.logging.Level.INFO] the logging level of this appender
  38. * <p><b>Note:</b> the level can be different from the logger in the case that you want a particular logger
  39. * to only log particular event of a level. For example an appender that only logs errors. BEWARE that if the
  40. * appenders level is lower than the logger is will not recieve any messages.</p>
  41. *
  42. * @param {String} [options.file="./log.log"] the file to log events to.
  43. * @param {String} [options.encoding="utf8"] the encoding of the file.
  44. * @param {Boolean} [options.overwrite=false] if true the log file is overwritten otherwise it is appended to.
  45. * @ignoreCode
  46. */
  47. 1define(Appender, {
  48. instance:{
  49. constructor:function (options) {
  50. 8 options = options || {};
  51. 8 !options.name && (options.name = "fileAppender");
  52. 8 this.__file = options.file || "./log.log";
  53. 8 this.__encoding = options.encoding || "utf8";
  54. 8 this.__overwrite = options.overwrite || false;
  55. 8 this.__writeStream = options.writeStream || fs.createWriteStream(this.__file, { flags:this.__overwrite ? "w" : 'a', encoding:this.__encoding});
  56. 8 this._super([options]);
  57. 8 this.__pattern += "\n";
  58. 8 base.listenForExit(base.hitch(this, "__onExit"));
  59. },
  60. __onExit:function () {
  61. 14 var ret = new Promise();
  62. 14 var ws = this.__writeStream;
  63. 14 this.__writeStream = null;
  64. 14 ws.on("close", base.hitch(ret, "callback"));
  65. 14 ws.destroySoon();
  66. 14 return ret.promise();
  67. },
  68. append:function (event) {
  69. 15 var ws = this.__writeStream;
  70. 15 if (this._canAppend(event) && ws && ws.writable) {
  71. 15 var message = format(this.__pattern, event);
  72. 15 var level = event.level;
  73. 15 ws.write(message);
  74. }
  75. }
  76. }
  77. }).registerType("FileAppender").as(module);
logging/appenders/index.js
Coverage100.00 SLOC11 LOC5 Missed0
  1. /**@ignore*/
  2. 1exports.Appender = require("./appender");
  3. /**@ignore*/
  4. 1exports.ConsoleAppender = require("./consoleAppender");
  5. /**@ignore*/
  6. 1exports.FileAppender = require("./fileAppender");
  7. /**@ignore*/
  8. 1exports.JSONAppender = require("./jsonAppender");
  9. /**@ignore*/
  10. 1exports.RollingFileAppender = require("./rollingFileAppender");
logging/appenders/jsonAppender.js
Coverage100.00 SLOC85 LOC19 Missed0
  1. 1var define = require("../../define.js").define,
  2. base = require("../../base"),
  3. string = base.string,
  4. escape = base.regexp.escapeString,
  5. FileAppender = require("./fileAppender"),
  6. format = string.format,
  7. Level = require("../level"),
  8. fs = require("fs");
  9. /**
  10. * @class Appends messages to a file in JSON format. The messages are logged to an array in a JSON file
  11. * <b>The file is always overwritten</b>
  12. *
  13. * @example
  14. * //example log.json
  15. * [
  16. * {
  17. * "timestamp" : "Wed Jun 08 2011 11:16:20 GMT-0500 (CDT)",
  18. * "level" : "INFO",
  19. * "name" : "comb",
  20. * "message" : "INFO MESSAGE!!!!"
  21. * }
  22. * ]
  23. *
  24. *
  25. * @name JSONAppender
  26. * @augments comb.logging.appenders.FileAppender
  27. * @memberOf comb.logging.appenders
  28. *
  29. * @param {Object} [options] options to assign to this Appender
  30. * @param {String} [options.name="appender"] the name of this Appender. If you want two of the same type of appender
  31. * on a logger it must have a different name.
  32. * @param {String} [options.pattern="{"timestamp" : "{timeStamp}", "level" : "{levelName}", "name" : "{name}", "message" : "{message}"}"]
  33. * <p>Available Options for formatting see {@link comb.string.format} for formatting options</p>
  34. * <ul>
  35. * <li>timeStamp - the timestamp of the event being logged</li>
  36. * <li>level - the {@link comb.logging.Level} of the event</li>
  37. * <li>levelName - the name of the level being logged</li>
  38. * <li>name - the name of the logger logging the event</li>
  39. * <li>message - the message being logged</li>
  40. * </ul>
  41. * @param {comb.logging.Level|String} [options.level=comb.logging.Level.INFO] the logging level of this appender
  42. * <p><b>Note:</b> the level can be different from the logger in the case that you want a particular logger
  43. * to only log particular event of a level. For example an appender that only logs errors. BEWARE that if the
  44. * appenders level is lower than the logger is will not recieve any messages.</p>
  45. *
  46. * @param {String} [options.file="./log.json"] the file to log events to.
  47. * @param {String} [options.encoding="utf8"] the encoding of the file.
  48. *
  49. * @ignoreCode
  50. */
  51. 1define(FileAppender, {
  52. instance:{
  53. constructor:function (options) {
  54. 5 options = options || {};
  55. 5 this.name = options.name || "JSONAppender";
  56. 5 this.__count = 0;
  57. 5 this.__file = options.file || "./log.json";
  58. 5 this.__encoding = options.encoding || "utf8";
  59. 5 this.__writeStream = options.writeStream || fs.createWriteStream(this.__file, { flags:"w", encoding:this.__encoding});
  60. 5 this.__writeStream.write("[\n");
  61. 5 this.level = options.level;
  62. //explicit overwrite of patter
  63. 5 this.__pattern = '{"timestamp" : "{timeStamp}", "level" : "{levelName}", "name" : "{name}", "message" : "{message}"}';
  64. 5 base.listenForExit(base.hitch(this, "__onExit"));
  65. },
  66. append:function (event) {
  67. 6 if (this._canAppend(event)) {
  68. 6 event.message = event.message.replace(/\n+/g, "\\n");
  69. 6 var message = (this.__count ? ",\n" : "\n") + format(this.__pattern, event);
  70. 6 this.__writeStream.write(message);
  71. 6 this.__count++;
  72. }
  73. },
  74. __onExit:function () {
  75. 5 this.__writeStream.write("]");
  76. 5 this._super(arguments);
  77. }
  78. }
  79. }).registerType("JSONAppender").as(module);
logging/appenders/rollingFileAppender.js
Coverage100.00 SLOC211 LOC79 Missed0
  1. 1var define = require("../../define.js").define,
  2. promise = require("../../promise"),
  3. Promise = promise.Promise,
  4. PromiseList = promise.PromiseList,
  5. base = require("../../base"),
  6. hitch = base.hitch,
  7. string = base.string,
  8. escape = base.regexp.escapeString,
  9. style = string.style,
  10. format = string.format,
  11. FileAppender = require("./fileAppender"),
  12. Level = require("../level"),
  13. fs = require("fs"),
  14. path = require("path");
  15. 1var conversion = {
  16. MB:1048576,
  17. KB:1024,
  18. GB:1073741824
  19. };
  20. 1var DEFAULT_SIZE = "10MB";
  21. 1var convertToBytes = function (str) {
  22. 3 var ret = DEFAULT_SIZE;
  23. 3 var match = str.match(/(\d+)(MB|KB|GB)$/);
  24. 3 if (match && match.length == 3) {
  25. 3 var size = parseInt(match[1], 10);
  26. 3 ret = size * conversion[match[2]];
  27. }
  28. 3 return ret;
  29. }
  30. /**
  31. * @class Appends messages to a file. Rolls files over when a size limit has been reached. Once the max file size has
  32. * been reached it is rolled over to a file called &lt;logName&gt;.log.n where n is a number.
  33. * </br></br>
  34. * <p>Example. RollingFileAppender is current writing to myLog.log, the log reaches is max size to it is
  35. * renamed to myLog.log.1 and a new myLog.log is created.</p>
  36. * </br>
  37. * If maxBackupIndex is reached then the log at that index is deleted. If maxBackupIndex is set to 0 then no log is
  38. * rolled over.</p>
  39. *
  40. * @name RollingFileAppender
  41. * @augments comb.logging.appenders.FileAppender
  42. * @memberOf comb.logging.appenders
  43. *
  44. * @param {Object} [options] options to assign to this Appender
  45. * @param {String} [options.name="appender"] the name of this Appender. If you want two of the same type of appender
  46. * on a logger it must have a different name.
  47. * @param {String} [options.pattern="[{[yyyy-MM-ddTHH:mm:ss:SSS (z)]timeStamp}] {[- 5]levelName} {[-20]name} - {message}"]
  48. * <p>Available Options for formatting see {@link comb.string.format} for formatting options</p>
  49. * <ul>
  50. * <li>timeStamp - the timestamp of the event being logged</li>
  51. * <li>level - the {@link comb.logging.Level} of the event</li>
  52. * <li>levelName - the name of the level being logged</li>
  53. * <li>name - the name of the logger logging the event</li>
  54. * <li>message - the message being logged</li>
  55. * </ul>
  56. * @param {comb.logging.Level|String} [options.level=comb.logging.Level.INFO] the logging level of this appender
  57. * <p><b>Note:</b> the level can be different from the logger in the case that you want a particular logger
  58. * to only log particular event of a level. For example an appender that only logs errors. BEWARE that if the
  59. * appenders level is lower than the logger is will not recieve any messages.</p>
  60. *
  61. * @param {String} [options.file="./log.log"] the file to log events to.
  62. * @param {String} [options.encoding="utf8"] the encoding of the file.
  63. * @param {Boolean} [options.overwrite=false] if true the log file is overwritten otherwise it is appended to.
  64. * @param {String} [options.maxSize="10MB"] the maxSize of a file. Valid options include "KB", "MB", or "GB"
  65. *
  66. * <pre class="code">
  67. * maxSize = "100MB"
  68. * //or
  69. * maxSize = "100KB"
  70. * //or
  71. * maxSize = "1GB"
  72. * </pre>
  73. *
  74. * @param {Number} [options.maxBackupIndex=10] the maximum number of files to rollOver.
  75. */
  76. 1define(FileAppender, {
  77. instance:{
  78. __watching:false,
  79. constructor:function (options) {
  80. 3 options = options || {};
  81. 3 this.maxSize = options.maxSize || DEFAULT_SIZE;
  82. 3 !options.name && (options.name = "rollingFileAppender");
  83. 3 this.maxBackupIndex = options.maxBackupIndex || 10;
  84. 3 this.__queue = [];
  85. 3 this.__inRollover = false;
  86. 3 this._super([options]);
  87. },
  88. __startCheck:function () {
  89. 1 if (!this.__watching) {
  90. 1 this.__watching = true;
  91. 1 fs.watchFile(this.__file, hitch(this, "__checkFile"));
  92. 1 fs.stat(this.__file, hitch(this, function (err, stat) {
  93. 1 this.__checkFile(stat);
  94. }));
  95. }
  96. },
  97. __checkFile:function (stats) {
  98. 5 var ret = new Promise();
  99. 5 if (!this.__inRollover) {
  100. 3 if (stats.size >= this.maxSize) {
  101. 2 if (this.maxBackupIndex > 0) {
  102. 1 this.__inRollover = true;
  103. 1 this.__onExit().chain(hitch(this, "__rollover")).then(hitch(this, function () {
  104. 1 var ws = fs.createWriteStream(this.__file, { flags:"w", encoding:this.__encoding});
  105. 1 ws.on("open", hitch(this, function () {
  106. 1 this.__writeStream = ws;
  107. 1 this.__inRollover = false;
  108. 1 this.__checkQueue();
  109. 1 ret.callback();
  110. }));
  111. }), hitch(ret, "errback", new Error("comb.logging.appenders.RollingFileAppender : error rolling over files")));
  112. } else {
  113. 1 this.__writeStream = fs.createWriteStream(this.__file, { flags:"w", encoding:this.__encoding});
  114. 1 ret.callback();
  115. }
  116. } else {
  117. 1 ret.callback();
  118. }
  119. } else {
  120. 2 ret.callback();
  121. }
  122. 5 return ret.promise();
  123. },
  124. append:function (event) {
  125. 12 if (this._canAppend(event)) {
  126. 12 !this.__watching && this.__startCheck();
  127. 12 var ws = this.__writeStream;
  128. 12 if (!this.__inRollover && ws && ws.writable) {
  129. 9 this._super(arguments);
  130. } else {
  131. 3 this.__queue.push(event);
  132. }
  133. }
  134. },
  135. __checkQueue:function () {
  136. 1 this.__queue.forEach(this.append, this);
  137. 1 this.__queue.length = 0;
  138. },
  139. __rollover:function () {
  140. 1 var ret = new Promise(), file = this.__file;
  141. 1 var dir = path.dirname(file), baseName = new RegExp("(" + escape(path.basename(path.basename(file))) + ")(?:\\.(\\d*))*");
  142. 1 fs.readdir(dir, hitch(this, function (err, files) {
  143. 1 files = files.filter(
  144. function (f) {
  145. 5 var match = f.match(baseName);
  146. 5 if (match) {
  147. 4 return true;
  148. } else {
  149. 1 return false;
  150. }
  151. });
  152. 1 files = files.sort(function (a, b) {
  153. 6 var ret = 0;
  154. 6 if (a > b) {
  155. 3 ret = 0;
  156. 3 } else if (a < b) {
  157. 3 ret = 1;
  158. }
  159. 6 return ret;
  160. });
  161. 1 var count = files.length, i = 0;
  162. 1 var checkFile = hitch(this, function () {
  163. 5 if (count > 0) {
  164. 4 var f = dir + "/" + files[i++];
  165. 4 if (count > this.maxBackupIndex) {
  166. //drop the file;
  167. 1 count--;
  168. 1 fs.unlink(f, function (err) {
  169. 1 err ? ret.errback(err) : checkFile();
  170. });
  171. } else {
  172. //rename the file
  173. 3 var rn = this.__file + "." + count--;
  174. 3 fs.rename(f, rn, function (err) {
  175. 3 err ? ret.errback(err) : checkFile();
  176. });
  177. }
  178. } else {
  179. 1 ret.callback();
  180. }
  181. });
  182. 1 checkFile();
  183. }));
  184. 1 return ret.promise();
  185. },
  186. getters:{
  187. maxSize:function () {
  188. 3 return this.__maxSize;
  189. }
  190. },
  191. setters:{
  192. maxSize:function (size) {
  193. 3 this.__maxSize = size ? convertToBytes(size) : DEFAULT_SIZE;
  194. }
  195. }
  196. }
  197. }).registerType("RollingFileAppender").as(module);
logging/config.js
Coverage100.00 SLOC213 LOC50 Missed0
  1. 1var define = require("../define.js").define, base = require("../base"), fs = require('fs'), Appender = require("./appenders/appender.js");
  2. 1var logging, Logger, Level, appenders;
  3. 1var checkProcessUncaughtException = (function () {
  4. 1 var isProcessUnCaaughtEception = false;
  5. 1 return function _checkProcessUncaughtException() {
  6. //if (!isProcessUnCaaughtEception) {
  7. 10 var rootLogger = Logger.getRootLogger();
  8. 10 if (!isProcessUnCaaughtEception) {
  9. 1 process.on("uncaughtException", function (err) {
  10. 2 if (rootLogger.appenders.length) {
  11. 1 rootLogger.error(err);
  12. } else {
  13. 1 console.error(err.stack);
  14. }
  15. });
  16. 1 isProcessUnCaaughtEception = true;
  17. }
  18. //}
  19. };
  20. })();
  21. 1var parseProperties = function (properties) {
  22. 4 for (var i in properties) {
  23. 4 var logger = Logger.getLogger(i);
  24. 4 var props = properties[i], level = props.level, appenderArr = props.appenders;
  25. 4 if (level) {
  26. 4 level = Level.toLevel(level);
  27. 4 if (level) {
  28. 4 logger.level = level;
  29. }
  30. }
  31. 4 if (appenderArr && base.isArray(appenderArr)) {
  32. 4 for (var j = appenderArr.length - 1; j >= 0; j--) {
  33. 14 var appenderProps = base.merge({}, appenderArr[j]), type = appenderProps.type;
  34. 14 appenderProps.type = null;
  35. 14 if (type) {
  36. 12 logger.addAppender(type, appenderProps);
  37. }
  38. }
  39. }
  40. }
  41. 4 checkProcessUncaughtException();
  42. };
  43. /**
  44. * @class default configurator for logging
  45. *
  46. * @name BasicConfigurator
  47. * @memberOf comb.logging
  48. * @ignoreCode
  49. *
  50. */
  51. 1var BasicConfigurator = (exports.BasicConfigurator = define(null, {
  52. instance:{
  53. /**@lends comb.logging.BasicConfigurator.prototype*/
  54. constructor:function () {
  55. 6 if (!Logger) {
  56. 1 logging = require("./index").logging;
  57. 1 Logger = logging.Logger;
  58. 1 Level = logging.Level;
  59. 1 appenders = logging.appenders;
  60. }
  61. },
  62. /**
  63. * Configure logging.
  64. *
  65. * @param {comb.logging.Appender} [appender=null] appender to add to the root logger, by default a console logger is added.
  66. */
  67. configure:function (appender) {
  68. 6 var rootLogger = Logger.getRootLogger();
  69. 6 rootLogger.removeAllAppenders();
  70. 6 if (base.isInstanceOf(appender, appenders.Appender)) {
  71. 2 rootLogger.addAppender(appender);
  72. } else {
  73. 4 rootLogger.addAppender(new appenders.ConsoleAppender());
  74. }
  75. 6 checkProcessUncaughtException();
  76. }
  77. }
  78. }));
  79. /**
  80. * @class Configures comb.Logger with the properties or properties contained within a file
  81. *
  82. * @example
  83. *
  84. * var propertyConfigurator = new comb.logging.PropertyConfigurator();
  85. *
  86. * propertyConfigurator.configure("/location/of/combLogger.json");
  87. *
  88. * //or
  89. *
  90. * var config = {
  91. * "my.logger" : {
  92. * level : "INFO",
  93. * appenders : [
  94. * {
  95. * //default file appender
  96. * type : "FileAppender",
  97. * file : "/var/log/myApp.log",
  98. * },
  99. * {
  100. * //default JSON appender
  101. * type : "JSONAppender",
  102. * file : "/var/log/myApp.JSON",
  103. * },
  104. * {
  105. * type : "FileAppender",
  106. * //override default patter
  107. * pattern : "{[EEEE, MMMM dd, yyyy h:m a]timeStamp} {[5]level}"
  108. * + " {[- 5]levelName} {[-20]name} : {message}",
  109. * //location of my log file
  110. * file : "/var/log/myApp-errors.log",
  111. * //override name so it will get added to the log
  112. * name : "errorFileAppender",
  113. * //overwrite each time
  114. * overwrite : true,
  115. * //explicity set the appender to only accept errors
  116. * level : "ERROR"
  117. * },
  118. * {
  119. * type : "JSONAppender",
  120. * file : "/var/log/myApp-error.json",
  121. * //explicity set the appender to only accept errors
  122. * level : "ERROR"
  123. * }
  124. * ]
  125. * }
  126. * //repeat for more loggers
  127. *
  128. * propertyConfigurator.configure(config);
  129. * }
  130. *
  131. * @name PropertyConfigurator
  132. * @augments comb.logging.BasicConfigurator
  133. * @memberOf comb.logging
  134. * @ignoreCode
  135. *
  136. */
  137. 1exports.PropertyConfigurator = define(BasicConfigurator, {
  138. instance:{
  139. /**@lends comb.logging.PropertyConfigurator.prototype*/
  140. /**
  141. * Call to configure logging
  142. *
  143. * @example
  144. *
  145. * //Example configuration
  146. * {
  147. * "my.logger" : {
  148. * level : "INFO",
  149. * appenders : [
  150. * {
  151. * //default file appender
  152. * type : "FileAppender",
  153. * file : "/var/log/myApp.log",
  154. * },
  155. * {
  156. * //default JSON appender
  157. * type : "JSONAppender",
  158. * file : "/var/log/myApp.JSON",
  159. * },
  160. * {
  161. * type : "FileAppender",
  162. * //override default patter
  163. * pattern : "{[EEEE, MMMM dd, yyyy h:m a]timeStamp} {[5]level}"
  164. * + " {[- 5]levelName} {[-20]name} : {message}",
  165. * //location of my log file
  166. * file : "/var/log/myApp-errors.log",
  167. * //override name so it will get added to the log
  168. * name : "errorFileAppender",
  169. * //overwrite each time
  170. * overwrite : true,
  171. * //explicity set the appender to only accept errors
  172. * level : "ERROR"
  173. * },
  174. * {
  175. * type : "JSONAppender",
  176. * file : "/var/log/myApp-error.json",
  177. * //explicity set the appender to only accept errors
  178. * level : "ERROR"
  179. * }
  180. * ]
  181. * }
  182. *
  183. * @param {Object|String} properties Object containing configuration or string containing a file name with the configuration.
  184. */
  185. configure:function (properties) {
  186. 6 var rootLogger = Logger.getRootLogger();
  187. 6 rootLogger.removeAllAppenders();
  188. 6 if (base.isHash(properties)) {
  189. 2 parseProperties(base.deepMerge({}, properties));
  190. } else {
  191. 4 fs.readFile(properties, function (err, res) {
  192. 4 if (err) {
  193. 1 throw err;
  194. } else {
  195. 3 try {
  196. 3 parseProperties(JSON.parse(res));
  197. } catch (e) {
  198. 1 throw e;
  199. }
  200. }
  201. });
  202. }
  203. }
  204. }
  205. });
logging/index.js
Coverage100.00 SLOC662 LOC162 Missed0
  1. 1var define = require("../define.js"),
  2. base = require("../base"),
  3. isString = base.isString,
  4. merge = base.merge,
  5. isUndefinedOrNull = base.isUndefinedOrNull,
  6. isHash = base.isHash,
  7. isInstanceOf = base.isInstanceOf,
  8. argsToArray = base.argsToArray,
  9. format = base.string.format,
  10. Level = require("./level"),
  11. appenders = require("./appenders"),
  12. Appender = appenders.Appender,
  13. configurators = require("./config");
  14. 1var rootTree;
  15. 1var LoggerTree = define.define(null, {
  16. instance:{
  17. constructor:function (root) {
  18. 14 this.__root = root;
  19. 14 this.__name = root.name;
  20. 14 this.__level = root.level;
  21. 14 this.__parent = root._parent;
  22. 14 this.__map = {};
  23. },
  24. __getSubLoggers:function () {
  25. 6 var map = this.__map, ret = [], n;
  26. 6 for (var i in map) {
  27. 2 n = map[i];
  28. 2 if (n) {
  29. 2 ret = ret.concat(n.tree.getCurrentLoggers());
  30. }
  31. }
  32. 6 return ret;
  33. },
  34. __getLoggers:function () {
  35. 4 return [this.__root].concat(this.__getSubLoggers())
  36. },
  37. getCurrentLoggers:function () {
  38. 4 return this.__getLoggers();
  39. },
  40. getSubLoggers:function () {
  41. 2 return this.__getSubLoggers();
  42. },
  43. getLogger:function (name) {
  44. 31 var ret;
  45. 31 if (name) {
  46. 29 var parts = name.split(".");
  47. 29 if (parts.length) {
  48. 29 var category = parts.shift();
  49. 29 var lNode = this.__map[category];
  50. 29 if (!lNode) {
  51. 13 lNode = this.__map[category] = new Logger(category, this);
  52. 13 lNode.addAppenders(this.__root.appenders);
  53. }
  54. 29 ret = lNode;
  55. 29 if (parts.length) {
  56. //keep searching
  57. 10 name = parts.join(".");
  58. 10 ret = lNode.tree.getLogger(name);
  59. }
  60. }
  61. } else {
  62. 2 ret = this.__root;
  63. }
  64. 31 return ret;
  65. },
  66. getRootLogger:function () {
  67. 23 return this.__root;
  68. },
  69. isDisabled:function (level) {
  70. },
  71. resetConfiguration:function () {
  72. },
  73. /**
  74. * level = string|Level
  75. */
  76. addAppender:function (appender) {
  77. 85 var map = this.__map;
  78. 85 for (var i in map) {
  79. 61 map[i].addAppender(appender);
  80. }
  81. },
  82. removeAppender:function (name) {
  83. 68 var map = this.__map;
  84. 68 for (var i in map) {
  85. 59 map[i].removeAppender(name);
  86. }
  87. },
  88. setters:{
  89. level:function (level) {
  90. 45 this.__level = level;
  91. 45 if (level && level instanceof Level) {
  92. 45 var map = this.__map;
  93. 45 for (var i in map) {
  94. 2 map[i].level = level;
  95. }
  96. }
  97. }
  98. },
  99. getters:{
  100. categories:function () {
  101. 2 return this.getCurrentLoggers().map(function (l) {
  102. 3 return l.fullName;
  103. });
  104. },
  105. name:function () {
  106. 35 var ret = this.__name;
  107. 35 if (this.__parent) {
  108. 21 var pName = this.__parent.name;
  109. 21 if (pName) {
  110. 8 ret = pName + "." + ret;
  111. }
  112. }
  113. 35 return ret;
  114. },
  115. level:function () {
  116. 13 return this.__level;
  117. },
  118. additive:function () {
  119. 13 return this.__root.additive;
  120. }
  121. }
  122. }
  123. });
  124. 1var comb = exports;
  125. /**
  126. * @ignore
  127. * @namespace logging package*/
  128. 1comb.logging = merge({
  129. Level:Level
  130. }, configurators);
  131. /**
  132. * @ignore
  133. * @namespace appenders for logging*/
  134. 1comb.logging.appenders = appenders;
  135. 1var logging = comb.logging;
  136. /**
  137. * @class This class is the entry point for all logging actions in comb.
  138. * <p><b>Logger should be retrieved by calling Logger.getLogger() NOT through the new keyword</b><p>
  139. * <p>
  140. * All loggers in comb follow a heirarchy of inheritance based on a dot notation.
  141. * <pre class="code">
  142. * rootLogger - ""
  143. * / \
  144. * "my" "myOther"
  145. * / \
  146. * "my.logger" "myOther.logger"
  147. * / \
  148. * "my.logger.Log" "myOther.logger.Log"
  149. *
  150. * </pre>
  151. * In the above Tree the rootLogger is the base for all logger. my and myOther inherit from rootLogger
  152. * my.logger inherits from my, and myOther.logger inherits from myOther. The logs do not have to be retrieved in
  153. * order. If I set rootLogger to ERROR level and added a console appender to it the appender and level will be
  154. * added to all logs. However if I set my to INFO level and add a fileAppender to it the level and appender will
  155. * only be added to logs in "my" subtree. If you set my.logger to not be additive then levels, and appenders will not
  156. * propogate down to the rest of the tree.
  157. *
  158. * </p>
  159. *
  160. * <p>For information on levels see {@link comb.logging.Level}.</p>
  161. * <p>For information on appenders see
  162. * <ul>
  163. * <li>{@link comb.logging.appenders.Appender}</li>
  164. * <li>{@link comb.logging.appenders.ConsoleAppender}</li>
  165. * <li>{@link comb.logging.appenders.FileAppender}</li>
  166. * <li>{@link comb.logging.appenders.JSONAppender}</li>
  167. * <li>{@link comb.logging.appenders.RollingFileAppender}</li>
  168. * </ul>
  169. * </p>
  170. * <p>For information on configurators see {@link comb.logging.BasicConfigurator} or {@link comb.logging.PropertyConfigurator}.</p>
  171. *
  172. * @example
  173. *
  174. * var logger = comb.logger;
  175. *
  176. * //configure you logging environement
  177. * logger.configure();
  178. *
  179. * //add a file appender to all loggers
  180. * logger.configure(logger.appender("FileAppender", {file : "/var/log/myLog.log"});
  181. *
  182. * //Retreiving a logger.
  183. * var combLogger = logger("comb");
  184. * var combCollectionLogger = logger("comb.collections");
  185. * var treeLogger = logger("comb.collections.Tree")
  186. * //add a JSON appender to tree logger just for fun!
  187. * .addAppender("JSONAppender", {file : "/var/log/myTreeLogger.json"})
  188. *
  189. * //set my treeLogger to DEBUG Level
  190. * treeLogger.level = "DEBUG";
  191. *
  192. *
  193. * @name Logger
  194. * @memberOf comb.logging
  195. *
  196. * @property {Array<comb.logging.Logger>} subLoggers all loggers this logger is the parent of.
  197. * @property {comb.logging.Level} level the level of this Logger
  198. * @property {Boolean} additive set to false to prevent changes to this logger from propogating down.
  199. * @property {Boolean} isDebug true if this Loggers level is DEBUG
  200. * @property {Boolean} isTrace true if this Loggers level is TRACE
  201. * @property {Boolean} isInfo true if this Loggers level is INFO
  202. * @property {Boolean} isWarn true if this Loggers level is WARN
  203. * @property {Boolean} isError true if this Loggers level is ERROR
  204. * @property {Boolean} isFatal true if this Loggers level is FATAL
  205. * @property {Boolean} isOff true if this Loggers level is OFF
  206. * @property {String} name the name of this logger this <b>does not</b> include the dot notated name
  207. * @property {String} fullName the full path name of this Logger.
  208. * @property {comb.logging.appenders.Appender} appenders list of appenders this logger currently contains.
  209. * @ignoreCode
  210. */
  211. 1var Logger = (logging.Logger = define.define(null, {
  212. instance:{
  213. /**@lends comb.logging.Logger.prototype*/
  214. constructor:function (name, parent) {
  215. 14 this.__additive = true;
  216. 14 this.__name = name;
  217. 14 this._parent = parent;
  218. 14 this._tree = new LoggerTree(this);
  219. 14 this.fullName = this._tree.name;
  220. 14 if (!parent || !parent.additive) {
  221. 1 this.level = Level.ALL;
  222. } else {
  223. 13 this.level = parent.level;
  224. }
  225. 14 this.__appenders = {};
  226. },
  227. /**
  228. * Log an info level message
  229. *
  230. * @param {String} message the message to log.
  231. *
  232. * @return {comb.logging.Logger} for chaining.
  233. */
  234. info:function (message) {
  235. 17 return this.log.apply(this, [Level.INFO].concat(argsToArray(arguments)));
  236. },
  237. /**
  238. * Log an debug level message
  239. *
  240. * @param {String} message the message to log.
  241. *
  242. * @return {comb.logging.Logger} for chaining.
  243. */
  244. debug:function (message) {
  245. 14 return this.log.apply(this, [Level.DEBUG].concat(argsToArray(arguments)));
  246. },
  247. /**
  248. * Log an error level message
  249. *
  250. * @param {String} message the message to log.
  251. *
  252. * @return {comb.logging.Logger} for chaining.
  253. */
  254. error:function (message) {
  255. 15 return this.log.apply(this, [Level.ERROR].concat(argsToArray(arguments)));
  256. },
  257. /**
  258. * Log an warn level message
  259. *
  260. * @param {String} message the message to log.
  261. *
  262. * @return {comb.logging.Logger} for chaining.
  263. */
  264. warn:function (message) {
  265. 14 return this.log.apply(this, [Level.WARN].concat(argsToArray(arguments)));
  266. },
  267. /**
  268. * Log an trace level message
  269. *
  270. * @param {String} message the message to log.
  271. *
  272. * @return {comb.logging.Logger} for chaining.
  273. */
  274. trace:function (message) {
  275. 14 return this.log.apply(this, [Level.TRACE].concat(argsToArray(arguments)));
  276. },
  277. /**
  278. * Log an fatal level message
  279. *
  280. * @param {String} message the message to log.
  281. *
  282. * @return {comb.logging.Logger} for chaining.
  283. */
  284. fatal:function (message) {
  285. 14 return this.log.apply(this, [Level.FATAL].concat(argsToArray(arguments)));
  286. },
  287. /**
  288. * Log a message
  289. *
  290. * @param {comb.logging.Level} level the level the message is
  291. * @param {String} message the message to log.
  292. *
  293. * @return {comb.logging.Logger} for chaining.
  294. */
  295. log:function (level, message) {
  296. 94 var args = argsToArray(arguments, 1);
  297. 94 level = Level.toLevel(level);
  298. 94 if (args.length > 1) {
  299. 18 message = format.apply(null, args);
  300. }
  301. 94 if (level.isGreaterOrEqualToo(this.level)) {
  302. 64 if (Level.TRACE.equals(level)) {
  303. 10 var err = new Error;
  304. 10 err.name = "Trace";
  305. 10 err.message = message || '';
  306. 10 Error.captureStackTrace(err, arguments.callee);
  307. 10 message = err.stack;
  308. 54 } else if (Level.ERROR.equals(level) && isInstanceOf(message, Error)) {
  309. 1 message = message.stack;
  310. }
  311. 64 var type = level.name.toLowerCase(), appenders = this.__appenders;
  312. 64 var event = {
  313. level:level,
  314. levelName:level.name,
  315. message:message,
  316. timeStamp:new Date(),
  317. name:this.fullName
  318. };
  319. 64 Object.keys(appenders).forEach(function (i) {
  320. 64 appenders[i].append(event);
  321. });
  322. }
  323. 94 return this;
  324. },
  325. /**
  326. * Add an appender to this logger. If this is additive then the appender is added to all subloggers.
  327. *
  328. * @example
  329. * comb.logger("my.logger")
  330. * .addAppender("ConsoleAppender")
  331. * .addAppender("FileAppender", {file:'/var/log/my.log'})
  332. * .addAppender("RollingFileAppender", {file:'/var/log/myRolling.log'})
  333. * .addAppender("JSONAppender", {file:'/var/log/myJson.log'});
  334. *
  335. * @param {comb.logging.Appender|String} If the appender is an {@link comb.logging.appenders.Appender} then it is added.
  336. * If the appender is a string then {@link comb.logging.appenders.Appender.createAppender} will be called to create it.
  337. *
  338. * @param {Object} [opts = null] If the first argument is a string then the opts will be used as constructor arguments for
  339. * creating the appender.
  340. *
  341. * @return {comb.logging.Logger} for chaining.
  342. */
  343. addAppender:function (appender, opts) {
  344. 104 var args = argsToArray(arguments);
  345. 104 if (isString(appender)) {
  346. 12 this.addAppender(Appender.createAppender(appender, opts));
  347. } else {
  348. 92 if (!isUndefinedOrNull(appender)) {
  349. 92 var name = appender.name;
  350. 92 if (!(name in this.__appenders)) {
  351. 85 this.__appenders[name] = appender;
  352. 85 if (!appender.level) {
  353. 17 appender.level = this.level;
  354. }
  355. 85 this._tree.addAppender(appender);
  356. }
  357. }
  358. }
  359. 104 return this;
  360. },
  361. /**
  362. * Short cut to add a list of appenders to this Logger
  363. * @param {Array<comb.logging.Appender>} appenders
  364. *
  365. * @return {comb.logging.Logger} for chaining.
  366. */
  367. addAppenders:function (appenders) {
  368. 14 appenders.forEach(this.addAppender.bind(this));
  369. 14 return this;
  370. },
  371. /**
  372. * Removes and appender from this logger.
  373. * @param {String} name the name of the appender
  374. * @return {comb.logging.Logger} for chaining.
  375. */
  376. removeAppender:function (name) {
  377. 68 if (name in this.__appenders) {
  378. 68 delete this.__appenders[name];
  379. 68 this._tree.removeAppender(name);
  380. }
  381. 68 return this;
  382. },
  383. /**
  384. * Removes a list of appenders from this logger.
  385. *
  386. * @param {String[]} appenders a list of names of appenders to remove
  387. * @return {comb.logging.Logger} for chaining.
  388. */
  389. removeAppenders:function (appenders) {
  390. 1 appenders.forEach(this.removeAppender, this);
  391. 1 return this;
  392. },
  393. /**
  394. * Removes all appenders from this logger and sub loggers if this Logger is additive.
  395. *
  396. * @return {comb.logging.Logger} for chaining.
  397. */
  398. removeAllAppenders:function () {
  399. 16 Object.keys(this.__appenders).forEach(this.removeAppender.bind(this));
  400. 16 return this;
  401. },
  402. /**
  403. * Determines if an appender is attached.
  404. *
  405. * @param {String} name the name of the appender.
  406. */
  407. isAppenderAttached:function (name) {
  408. 38 return (name in this.__appenders);
  409. },
  410. /**
  411. * Gets an appender from this logger
  412. *
  413. * @param {String} name the name of the appender.
  414. *
  415. * @return {comb.logging.Appender|undefined} returns the appender with the specified name or
  416. * undefined if it is not found.
  417. */
  418. getAppender:function (name) {
  419. 8 var ret;
  420. 8 if (name in this.__appenders) {
  421. 8 ret = this.__appenders[name];
  422. }
  423. 8 return ret;
  424. },
  425. /**
  426. * @ignore
  427. * */
  428. setters:{
  429. level:function (level) {
  430. 46 level = Level.toLevel(level);
  431. 46 if (this.__additive) {
  432. 45 this.__level = level;
  433. 45 var appenders = this.__appenders;
  434. 45 for (var i in appenders) {
  435. 35 appenders[i].level = level;
  436. }
  437. 45 this._tree.level = level;
  438. } else {
  439. 1 this.__level = level;
  440. }
  441. },
  442. additive:function (additive) {
  443. 2 this.__additive = additive;
  444. }
  445. },
  446. /**@ignore*/
  447. getters:{
  448. /**@ignore*/
  449. /**@ignore*/
  450. subLoggers:function () {
  451. 2 return this._tree.getSubLoggers();
  452. },
  453. /**@ignore*/
  454. level:function () {
  455. 202 return this.__level;
  456. },
  457. /**@ignore*/
  458. additive:function () {
  459. 15 return this.__additive;
  460. },
  461. isAll:function () {
  462. 8 return Level.ALL.isGreaterOrEqualToo(this.level);
  463. },
  464. /**@ignore*/
  465. isDebug:function () {
  466. 8 return Level.DEBUG.isGreaterOrEqualToo(this.level);
  467. },
  468. /**@ignore*/
  469. isTrace:function () {
  470. 8 return Level.TRACE.isGreaterOrEqualToo(this.level);
  471. },
  472. /**@ignore*/
  473. isInfo:function () {
  474. 8 return Level.INFO.isGreaterOrEqualToo(this.level);
  475. },
  476. /**@ignore*/
  477. isWarn:function () {
  478. 8 return Level.WARN.isGreaterOrEqualToo(this.level);
  479. },
  480. /**@ignore*/
  481. isError:function () {
  482. 8 return Level.ERROR.isGreaterOrEqualToo(this.level);
  483. },
  484. /**@ignore*/
  485. isFatal:function () {
  486. 8 return Level.FATAL.isGreaterOrEqualToo(this.level);
  487. },
  488. /**@ignore*/
  489. isOff:function () {
  490. 9 return Level.OFF.equals(this.level);
  491. },
  492. /**@ignore*/
  493. name:function () {
  494. 14 return this.__name;
  495. },
  496. /**@ignore*/
  497. tree:function () {
  498. 13 return this._tree;
  499. },
  500. /**@ignore*/
  501. appenders:function () {
  502. 20 var ret = [];
  503. 20 for (var i in this.__appenders) {
  504. 9 ret.push(this.__appenders[i]);
  505. }
  506. 20 return ret;
  507. },
  508. categories:function () {
  509. 2 return this._tree.categories;
  510. }
  511. }
  512. },
  513. static:{
  514. /**@lends comb.logging.Logger*/
  515. /**
  516. * Return the root of all loggers
  517. */
  518. getRootLogger:function () {
  519. 23 return rootTree.getRootLogger();
  520. },
  521. /**
  522. * Retrieves/Creates a logger based on the name passed in
  523. *
  524. * @param {String} name the name of the logger
  525. */
  526. getLogger:function (name) {
  527. 21 return rootTree.getLogger(name);
  528. }
  529. }
  530. }));
  531. /**
  532. *
  533. * @function
  534. * @description Alias to {@link comb.logging.Logger.getLogger}. See {@link comb.logging.Logger} for more information.
  535. *
  536. * @example
  537. *
  538. * comb.logger("my.logger");
  539. *
  540. * @memberOf comb
  541. * @namespace
  542. */
  543. 1exports.logger = Logger.getLogger.bind(Logger);
  544. /**
  545. * @function
  546. * @description Shortcut to configure loggers.
  547. *
  548. * @example
  549. *
  550. * //same as new comb.logging.BasicConfigurator().configure();
  551. * comb.logger.configure();
  552. *
  553. * //new comb.logging.PropertyConfigurator().configure("/location/to/logger/configuration.json");
  554. * comb.logger.configure("/location/to/logger/configuration.json");
  555. *
  556. * @memberOf comb.logger
  557. */
  558. 1exports.logger.configure = function configure() {
  559. 4 var args = argsToArray(arguments), configurator;
  560. 4 if (!args.length) {
  561. 1 configurator = new configurators.BasicConfigurator();
  562. } else {
  563. 3 var first = args[0];
  564. 3 if (isHash(first) || isString(first)) {
  565. 2 configurator = new configurators.PropertyConfigurator();
  566. } else {
  567. 1 configurator = new configurators.BasicConfigurator();
  568. }
  569. }
  570. 4 configurator.configure.apply(configurator, args);
  571. 4 return this;
  572. };
  573. /**
  574. * @function
  575. * @description Factory method for creating appenders. See {@link comb.logging.appenders.Appender.createAppender} for
  576. * arguments.
  577. *
  578. * @example
  579. *
  580. * var appender = comb.logger.appender("ConsoleAppender");
  581. * comb.logger("my.logger").addAppender(appender);
  582. *
  583. * @memberOf comb.logger
  584. *
  585. */
  586. 1exports.logger.appender = Appender.createAppender.bind(Appender);
  587. 1var rootLogger = new Logger("");
  588. 1rootTree = rootLogger._tree;
  589. /**
  590. * The root for all loggers.
  591. * @memberOf comb.logger
  592. * @type comb.logging.Logger
  593. */
  594. 1exports.logger.rootLogger = rootLogger;
logging/level.js
Coverage100.00 SLOC188 LOC33 Missed0
  1. 1var define = require("../define.js").define, base = require("../base");
  2. 1var LEVELS = {
  3. ALL:-100000,
  4. DEBUG:1,
  5. TRACE:2,
  6. INFO:3,
  7. WARN:4,
  8. ERROR:5,
  9. FATAL:6,
  10. OFF:100000
  11. };
  12. 1var LEVELS_REVERSE = {
  13. "-100000":"ALL",
  14. "1":"DEBUG",
  15. "2":"TRACE",
  16. "3":"INFO",
  17. "4":"WARN",
  18. "5":"ERROR",
  19. "6":"FATAL",
  20. "100000":"OFF"
  21. };
  22. /**
  23. * @class Level class used to describe logging levels. The levels determine what types of events are logged to the appenders
  24. * for example the if Level.ALL is used then all event will be logged, however if Level.INFO was used then <b>ONLY</b>
  25. * INFO, WARN, ERROR, and FATAL events will be logged. To turn off logging for a logger use Level.OFF.
  26. *
  27. * <p><b>Not typically instantiated directly, but through staticly defined levels</b></p>
  28. * @example
  29. * //Levels in ascending order
  30. * comb.logging.Level.ALL
  31. * comb.logging.Level.DEBUG
  32. * comb.logging.Level.TRACE
  33. * comb.logging.Level.INFO
  34. * comb.logging.Level.WARN
  35. * comb.logging.Level.ERROR
  36. * comb.logging.Level.FATAL
  37. * comb.logging.Level.OFF
  38. *
  39. * //or
  40. * Level.getLevel("INFO");
  41. *
  42. * @name Level
  43. * @memberOf comb.logging
  44. *
  45. * @property {Number} level the numerical representation of this level.
  46. * @property {String} name the name of level.
  47. * @ignoreCode
  48. */
  49. 1var Level = (exports = module.exports = define(null, {
  50. instance:{
  51. /**@lends comb.logging.Level.prototype*/
  52. constructor:function (level, name) {
  53. 9 this.level = level;
  54. 9 this.name = name;
  55. },
  56. /**
  57. * Determing if this level is >= another level
  58. * @param {comb.logging.Level} level the level to test against
  59. *
  60. * @returns {Boolean} true if this is >= false otherwise.
  61. */
  62. isGreaterOrEqualToo:function (level) {
  63. 290 var ret = false;
  64. 290 if (level && base.isNumber(level.level)) {
  65. 290 if (this.level >= level.level) {
  66. 189 ret = true;
  67. }
  68. }
  69. 290 return ret;
  70. },
  71. /**
  72. * Determing if this level is equal to another level based off of the numerical rank.
  73. *
  74. * @param {comb.logging.Level} level the level to compare
  75. *
  76. * @returns {Boolean} true if this is equal to that false otherwise.
  77. */
  78. equals:function (level) {
  79. 148 return level.level == this.level;
  80. }
  81. },
  82. static:{
  83. /**@lends comb.logging.Level*/
  84. /**
  85. * Converts a numerical or string representation of a level, if a default level is provided,
  86. * then if a level cannot be determined then the default level is used.
  87. *
  88. * @param {Number|String|comb.logging.Level} level the level to try to convert
  89. * @param {comb.logging.Level} [defaultLevel] default level to use if one cannot be determined,
  90. *
  91. * @returns {comb.logging.Level|null} returns a level if one can be determined null otherwise.
  92. */
  93. toLevel:function (level, defaultLevel) {
  94. 194 var ret = null;
  95. 194 var args = base.argsToArray(arguments);
  96. 194 if (args.length === 1) {
  97. 192 var level = args[0];
  98. 192 if (base.isNumber(level)) {
  99. 11 var strLevel = LEVELS_REVERSE[level];
  100. 11 ret = Level[strLevel];
  101. 181 } else if (base.isString(level)) {
  102. 48 ret = Level[level.toUpperCase()];
  103. } else {
  104. 133 ret = level;
  105. }
  106. } else {
  107. 2 ret = (Level.toLevel(args[0]) || args[1]);
  108. }
  109. 194 return ret;
  110. },
  111. /**
  112. * Adds a new level to the Level object.
  113. *
  114. * @example
  115. *
  116. * logger = Logger.getLogger("my.logger");
  117. *
  118. * //create the custom level
  119. * Level.addLevel("custom_Level", 20);
  120. *
  121. * //now set the level on a logger
  122. * logger.level = Level.CUSTOM_LEVEL;
  123. *
  124. * @param {string} label the label of the level, <b>Note:</b> the label will be coverted to uppercase.
  125. * @param {number} level the level of the level
  126. *
  127. * @return {undefined|comb.logging.Level} the level that was created.
  128. *
  129. */
  130. addLevel:function (label, level) {
  131. 1 var ret;
  132. 1 if (base.isString(label) && base.isNumber(level)) {
  133. 1 label = label.toUpperCase();
  134. 1 LEVELS_REVERSE[level] = label;
  135. 1 LEVELS[label] = level;
  136. 1 ret = (this[label] = new Level(level, label));
  137. }
  138. 1 return ret;
  139. },
  140. /**
  141. * Level to allow logging of all events.
  142. */
  143. ALL:null,
  144. /**
  145. * Logs only events debug or greater.
  146. */
  147. DEBUG:null,
  148. /**
  149. * Like debug but provides a finer level of detail
  150. */
  151. TRACE:null,
  152. /**
  153. * Only info, or error related events
  154. */
  155. INFO:null,
  156. /**
  157. * Only warn or error related events
  158. */
  159. WARN:null,
  160. /**
  161. * Error or fatal events
  162. */
  163. ERROR:null,
  164. /**
  165. * Only fatal events
  166. */
  167. FATAL:null,
  168. /**
  169. * No events will be logged.
  170. */
  171. OFF:null
  172. }
  173. }));
  174. 1for (var i in LEVELS_REVERSE) {
  175. 8 Level[LEVELS_REVERSE[i]] = new Level(parseInt(i, 10), LEVELS_REVERSE[i]);
  176. }
plugins/Broadcaster.js
Coverage100.00 SLOC113 LOC26 Missed0
  1. 1var func = require("../base/functions"),
  2. define = require("../define").define;
  3. 1var Broadcaster = define(null, {
  4. instance : {
  5. /** @lends comb.plugins.Broadcaster.prototype */
  6. /**
  7. * Plugin to allow a class to easily broadcast events
  8. *
  9. * @example
  10. *
  11. * var Mammal = define(comb.plugins.Broadcaster, {
  12. * instance : {
  13. *
  14. * constructor: function(options) {
  15. * options = options || {};
  16. * this._super(arguments);
  17. * this._type = options.type || "mammal";
  18. * },
  19. *
  20. * speak : function() {
  21. * var str = "A mammal of type " + this._type + " sounds like";
  22. * this.broadcast("speak", str);
  23. * this.onSpeak(str);
  24. * return str;
  25. * },
  26. *
  27. * onSpeak : function(){}
  28. * }
  29. * });
  30. *
  31. *
  32. * var m = new Mammal({color : "gold"});
  33. * m.listen("speak", function(str){
  34. * //called back from the broadcast event
  35. * console.log(str);
  36. * });
  37. * m.speak();
  38. *
  39. * @constructs
  40. */
  41. constructor : function() {
  42. 2 this.__listeners = {};
  43. },
  44. /**
  45. * Broadcasts an event from an object
  46. *
  47. * @param name the name of the event to broadcast
  48. * @param {Object|String|Function|Date|Number} [args] variable number of arguments to pass to listeners, can be anything
  49. */
  50. broadcast : function(topic, args) {
  51. 2 var args = Array.prototype.slice.call(arguments, 0), topic = args.shift();
  52. 2 if (topic && topic in this.__listeners) {
  53. 2 var list = this.__listeners[topic], i = list.length - 1;
  54. 2 while (i >= 0) {
  55. 2 list[i--].cb.apply(this, args);
  56. }
  57. }
  58. },
  59. /**
  60. * Listens to a broadcasted event
  61. * Simimlar to {@link comb.listen}
  62. *
  63. * @param {String} topic the topic to listen to
  64. * @param {Function} callback the function to callback on event publish
  65. *
  66. * @returns {Array} handle to disconnect a topic
  67. */
  68. listen : function(topic, callback) {
  69. 3 if (!func.isFunction(callback)) throw new Error("callback must be a function");
  70. 3 var handle = {
  71. topic : topic,
  72. cb : callback
  73. };
  74. 3 var list = this.__listeners[topic];
  75. 3 if (!list) {
  76. 2 list = (this.__listeners[topic] = [handle]);
  77. 2 handle.pos = 0;
  78. } else {
  79. 1 handle.pos = list.push(handle);
  80. }
  81. 3 return handle;
  82. },
  83. /**
  84. * Disconnects a listener
  85. * Similar to {@link comb.unListen}
  86. *
  87. * @param handle disconnect a handle returned from Broadcaster.listen
  88. */
  89. unListen : function(handle) {
  90. 1 if (handle) {
  91. 1 var topic = handle.topic;
  92. 1 if (topic in this.__listeners) {
  93. 1 var listeners = this.__listeners, list = listeners[topic];
  94. 1 if (list) {
  95. 1 for (var i = list.length - 1; i >= 0; i--) {
  96. 1 if (list[i] == handle) {
  97. 1 list.splice(i, 1);
  98. 1 break;
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. }
  106. });
  107. 1exports = module.exports = Broadcaster;
plugins/Middleware.js
Coverage100.00 SLOC212 LOC40 Missed0
  1. 1var func = require("../base/functions"),
  2. obj = require("../base/object"),
  3. Promise = require("../promise").Promise,
  4. define = require("../define").define;
  5. 1var Middleware = define(null, {
  6. instance:{
  7. /** @lends comb.plugins.Middleware.prototype */
  8. __hooks:{pre:{}, post:{}},
  9. /**
  10. * @class Plugin to enable middleware on a class
  11. *
  12. * @example
  13. *
  14. * var Mammal = define(comb.plugins.Middleware, {
  15. * instance : {
  16. *
  17. * constructor: function(options) {
  18. * options = options || {};
  19. * this.super(arguments);
  20. * this._type = options.type || "mammal";
  21. * },
  22. *
  23. * speak : function() {
  24. * var ret = new comb.Promise();
  25. * this._hook("pre", "speak")
  26. * .then(comb.hitch(this, "_hook", "post", "speak"), hitch(ret, "errback"))
  27. * .then(comb.hitch(ret, "callback"), comb.hitch(ret, "errback"));
  28. * return ret;
  29. * }
  30. * }
  31. *});
  32. *
  33. * Mammal.pre('speak', function(next){
  34. * //do something meaningful
  35. * next();
  36. * });
  37. * var m = new Mammal({color : "gold"});
  38. * m.speak();
  39. *
  40. * @constructs
  41. */
  42. constructor:function () {
  43. 6 this.__hooks = obj.merge({}, this.__hooks);
  44. 6 this._super(arguments);
  45. },
  46. /**
  47. * <p>Protected!</p>
  48. *
  49. * <p>Call to initiate middleware for the topic</p>
  50. * <p><b>NOTE:</b> this function takes a variable number of arguments
  51. * whatever comes after the op param will be passed into
  52. * the listening function, with the last argument to the listenting
  53. * function being the next function</p>
  54. *
  55. *
  56. * @public
  57. * @param {"pre"|"post"} state the state in which the hook should be called
  58. * @param {String} op the operation that is being acted upong
  59. * @param args arguments to be passed into the listening functions.
  60. * @returns {comb.Promise} a promise to use after middleware chain completes
  61. *
  62. */
  63. _hook:function (state, op, args) {
  64. 11 args = args || [];
  65. 11 var promise = new Promise();
  66. 11 var funcs, length;
  67. 11 if (this.__hooks[state] && (funcs = this.__hooks[state][op]) != null && (length = funcs.length) > 0) {
  68. 7 var count = 0;
  69. 7 var next = func.hitch(this, function (err) {
  70. 15 if (err) {
  71. 1 promise.errback(err);
  72. } else {
  73. 14 process.nextTick(func.hitch(this, function () {
  74. //if Ive looped through all of them callback
  75. 14 if (count == length) {
  76. 6 promise.callback();
  77. } else {
  78. //call next
  79. 8 var nextArgs = args.slice(0);
  80. 8 nextArgs.unshift(next);
  81. 8 funcs[count++].apply(this, nextArgs);
  82. }
  83. }));
  84. }
  85. });
  86. 7 next();
  87. } else {
  88. 4 promise.callback();
  89. }
  90. 11 return promise.promise();
  91. },
  92. /**
  93. * Use to listen to before an event occurred i.e. pre save
  94. *
  95. * <b>NOTE:</b></br>
  96. * <ul>
  97. * <li>You must call next in order for the middleware chain to complete</li>
  98. * <li>This connects to events on the instance of an object, not all instances!</li>
  99. * <li>Hooks are called in the order they are received!</li>
  100. * <li> When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
  101. * </ul>
  102. *
  103. * @example
  104. * instance.pre("save", function(args,...., next){
  105. * //do something...
  106. * //you have to call next!!!!!
  107. * next();
  108. * });
  109. *
  110. * */
  111. pre:function (fun, callback) {
  112. 1 var hook = this.__hooks.pre[fun];
  113. 1 if (!hook) {
  114. 1 hook = this.__hooks.pre[fun] = [];
  115. }
  116. 1 hook.push(callback);
  117. },
  118. /**
  119. * <p>Use to listen to after an event has occurred i.e. post save</p>
  120. * <b>NOTE:</b></br>
  121. * <ul>
  122. * <li>You must call next in order for the middleware chain to complete</li>
  123. * <li>This connects to events on the instance of an object, NOT all instances!</li>
  124. * <li>Hooks are called in the order they are received!</li>
  125. * <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
  126. * </ul>
  127. * @example
  128. *
  129. * instance.post("save", function(next){
  130. * //do something...
  131. * //you have to call next!!!!!
  132. * next();
  133. * });
  134. * */
  135. post:function (fun, callback) {
  136. 1 var hook = this.__hooks.post[fun];
  137. //if I havent initialized it create it;
  138. 1 if (hook == undefined) {
  139. 1 hook = this.__hooks.post[fun] = [];
  140. }
  141. 1 hook.push(callback);
  142. }
  143. },
  144. static:{
  145. /** @lends comb.plugins.Middleware */
  146. /**
  147. *<p> Use to listen to after an event has occurred i.e. post save</p>
  148. *
  149. * <b>NOTE:</b></br>
  150. * <ul>
  151. * <li>You must call next in order for the middleware chain to complete</li>
  152. * <li>This connects to events on ALL instances of an object</li>
  153. * <li>Hooks are called in the order they are received!</li>
  154. * <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
  155. * </ul>
  156. *
  157. * @example
  158. * Class.pre("save", function(next){
  159. * ...
  160. * //you must call next
  161. * });
  162. * */
  163. pre:function (name, cb) {
  164. 2 var hooks = this.prototype.__hooks;
  165. 2 var hook = hooks.pre[name];
  166. 2 if (!hook) {
  167. 1 hook = hooks.pre[name] = [];
  168. }
  169. 2 hook.push(cb);
  170. },
  171. /**
  172. *<p>Use to listen to after an event has occurred i.e. post save</p>
  173. *
  174. *<b>NOTE:</b></br>
  175. * <ul>
  176. * <li>You must call next in order for the middleware chain to complete</li>
  177. * <li>This connects to events on ALL instances of an object</li>
  178. * <li>Hooks are called in the order they are received!</li>
  179. * <li>When connecting your callback will be called in the scope of the class</br>if you want a certain scope use {@link comb.hitch}</li>
  180. * </ul>
  181. *
  182. * @example
  183. * Class.post("save", function(next){
  184. * ...
  185. * //you must call next
  186. * });
  187. * */
  188. post:function (name, cb) {
  189. 1 var hooks = this.prototype.__hooks;
  190. 1 var hook = hooks.post[name];
  191. 1 if (!hook) {
  192. 1 hook = hooks.post[name] = [];
  193. }
  194. 1 hook.push(cb);
  195. }
  196. }
  197. });
  198. 1module.exports = exports = Middleware;
plugins/index.js
Coverage100.00 SLOC6 LOC2 Missed0
  1. 1var comb = exports;
  2. /**@namespace plugins for classes using {@link comb.define}*/
  3. 1comb.plugins = {
  4. Broadcaster : require("./Broadcaster"),
  5. Middleware : require("./Middleware")
  6. };
promise.js
Coverage100.00 SLOC736 LOC192 Missed0
  1. 1var hitch = require("./base/functions").hitch,
  2. define = require("./define").define,
  3. base = require("./base"),
  4. argsToArray = base.argsToArray,
  5. isUndefinedOrNull = base.isUndefinedOrNull,
  6. isArray = base.isArray,
  7. isFunction = base.isFunction,
  8. isInstanceOf = base.isInstanceOf;
  9. 1var Promise = define(null, {
  10. instance:{
  11. /** @lends comb.Promise.prototype */
  12. __fired:false,
  13. __results:null,
  14. __error:null,
  15. __errorCbs:null,
  16. __cbs:null,
  17. /**
  18. * Promise object used to provide seperation of success and error resolution paths for async operations.
  19. *
  20. * @example
  21. * var myFunc = function(){
  22. * var promise = new Promise();
  23. * //callback the promise after 10 Secs
  24. * setTimeout(hitch(promise, "callback"), 10000);
  25. * return promise.promise();
  26. * }
  27. * var myFunc2 = function(){
  28. * var promises =[];
  29. * for(var i = 0; i < 10; i++){
  30. * promises.push(myFunc);
  31. * }
  32. * //create a new promise list with all 10 promises
  33. * return new PromiseList(promises).promise();
  34. * }
  35. *
  36. * myFunc.then(function(success){}, function(error){})
  37. * //chain promise operations
  38. * myFunc.chain(myfunc).then(function(success){}, function(error){})
  39. *
  40. * myFunc2.then(function(success){}, function(error){})
  41. * //chain promise operations
  42. * myFunc2.chain(myfunc).then(function(success){}, function(error){})
  43. * @constructs
  44. */
  45. constructor:function () {
  46. 246 this.__errorCbs = [];
  47. 246 this.__cbs = [];
  48. },
  49. /**
  50. * @private
  51. */
  52. __resolve:function () {
  53. 216 if (!this.__fired) {
  54. 216 this.__fired = true;
  55. 216 var cbs = this.__error ? this.__errorCbs : this.__cbs,
  56. len = cbs.length, i,
  57. results = this.__error || this.__results;
  58. 216 for (i = 0; i < len; i++) {
  59. 127 this.__callNextTick(cbs[i], results);
  60. }
  61. }
  62. },
  63. __callNextTick:function (cb, results) {
  64. 127 process.nextTick(hitch(this, function () {
  65. 127 cb.apply(this, results);
  66. }));
  67. },
  68. /**
  69. * Add a callback to the callback chain of the promise
  70. *
  71. *
  72. * @param {Function|comb.Promise} cb the function or promise to callback when the promise is resolved.
  73. *
  74. * @return {comb.Promise} this promise for chaining
  75. */
  76. addCallback:function (cb) {
  77. 209 if (cb) {
  78. 206 if (exports.isPromiseLike(cb)) {
  79. 30 cb = hitch(cb, "callback");
  80. }
  81. 206 if (this.__fired && this.__results) {
  82. 72 cb.apply(this, this.__results);
  83. } else {
  84. 134 this.__cbs.push(cb);
  85. }
  86. }
  87. 209 return this;
  88. },
  89. /**
  90. * Add a callback to the errback chain of the promise
  91. *
  92. * @param {Function|comb.Promise} cb the function or promise to callback when the promise errors
  93. *
  94. * @return {comb.Promise} this promise for chaining
  95. */
  96. addErrback:function (cb) {
  97. 207 if (cb) {
  98. 198 if (exports.isPromiseLike(cb)) {
  99. 44 cb = hitch(cb, "errback");
  100. }
  101. 198 if (this.__fired && this.__error) {
  102. 9 cb.apply(this, this.__error);
  103. } else {
  104. 189 this.__errorCbs.push(cb);
  105. }
  106. }
  107. 207 return this;
  108. },
  109. /**
  110. *
  111. * Adds a callback or promise to be resolved for both success
  112. * and error.
  113. *
  114. * @param {Function|comb.Promise} cb callback or promise to be resolved for both success
  115. * and error.
  116. * @return {comb.Promise} this promise for chaining
  117. */
  118. both:function (cb) {
  119. 6 this.addCallback(cb);
  120. 6 if (exports.isPromiseLike(cb)) {
  121. 2 this.addErrback(hitch(cb, "callback"));
  122. } else {
  123. 4 this.addErrback(cb);
  124. }
  125. 6 return this;
  126. },
  127. /**
  128. * When called all functions registered as callbacks are called with the passed in results.
  129. *
  130. * @param {*} args variable number of results to pass back to listeners of the promise
  131. */
  132. callback:function (args) {
  133. 180 args = argsToArray(arguments);
  134. 180 if (this.__fired) {
  135. 1 throw new Error("Already fired!");
  136. }
  137. 179 this.__results = args;
  138. 179 this.__resolve();
  139. 179 return this.promise();
  140. },
  141. /**
  142. * When called all functions registered as errbacks are called with the passed in error(s)
  143. *
  144. * @param {*} args number of errors to pass back to listeners of the promise
  145. */
  146. errback:function (args) {
  147. 38 if (this.__fired) {
  148. 1 throw args || new Error("Already fired");
  149. }
  150. 37 this.__error = argsToArray(arguments);
  151. 37 this.__resolve();
  152. 37 return this.promise();
  153. },
  154. /**
  155. * Resolved a promise using the node style callback.
  156. *
  157. * @example
  158. *
  159. * var promise = new Promise();
  160. * fs.readFile("file.txt", "utf8", promise.resolve.bind(promise));
  161. * promise.then(function(file){
  162. * console.log(file);
  163. * });
  164. *
  165. * @param {Error} [err=null] If specified then the promise will error out
  166. * @param {...} [args] if err is null then the aruments will be used to resolve the promise.
  167. *
  168. * @return {comb.Promise} for chaining.
  169. */
  170. resolve:function (err, args) {
  171. 2 if (err) {
  172. 1 this.errback(err);
  173. } else {
  174. 1 this.callback.apply(this, argsToArray(arguments, 1));
  175. }
  176. 2 return this;
  177. },
  178. /**
  179. * Call to specify action to take after promise completes or errors
  180. *
  181. * @param {Function} [callback=null] function to call after the promise completes successfully
  182. * @param {Function} [errback=null] function to call if the promise errors
  183. *
  184. * @return {comb.Promise} this promise for chaining
  185. */
  186. then:function (callback, errback) {
  187. 128 if (exports.isPromiseLike(callback)) {
  188. 28 this.addCallback(callback);
  189. 28 this.addErrback(callback);
  190. } else {
  191. 100 this.addCallback(callback);
  192. 100 this.addErrback(errback);
  193. }
  194. 128 return this;
  195. },
  196. /**
  197. * Call this function as a classic node callback where the first argument
  198. * will be an error, or null if no error occured. The other arugments will
  199. * be the result from the promise.
  200. *
  201. * @example
  202. *
  203. * promise.classic(function(err, res){
  204. * if(err){
  205. * console.log(err);
  206. * }else{
  207. * console.log(res);
  208. * }
  209. * });
  210. *
  211. * @param cb callback where the first argument
  212. * will be an error, or null if no error occured. The other arugments will
  213. * be the result from the promise.
  214. * @return {comb.Promise} the promise to chain
  215. */
  216. classic:function (cb) {
  217. 3 if ("function" === typeof cb) {
  218. 3 this.addErrback(function (err) {
  219. 1 cb(err);
  220. });
  221. 3 this.addCallback(function () {
  222. 2 cb.apply(this, [null].concat(argsToArray(arguments)));
  223. });
  224. }
  225. 3 return this;
  226. },
  227. /**
  228. * Call to chaining of promises
  229. *
  230. * @example
  231. *
  232. * var p = new Promise();
  233. *
  234. * p
  235. * .chain(function(previousPromiseResults){
  236. * return previousPromiseResults + " world";
  237. * }, errorHandler)
  238. * .chain(function(previousPromiseResults){
  239. * return when(dbCall());
  240. * }).classic(function(err, results){
  241. * //all promises are done
  242. * });
  243. *
  244. * p.callback("Hello");
  245. *
  246. * @param callback method to call this one completes. If you return a promise the execution will delay until the returned promise has resolved.
  247. * @param [errback=null] method to call if this promise errors. If errback is not specified then the returned promises
  248. * errback method will be used.
  249. *
  250. * @return {comb.Promise} A new that wraps the promise for chaining
  251. */
  252. chain:function (callback, errback) {
  253. 23 var promise = new Promise();
  254. 23 this.addCallback(function (results) {
  255. 20 when(isFunction(callback) ? callback.call(this, results) : callback).then(promise);
  256. });
  257. // If no errback passed, then invoke our promise's errback to pass
  258. // on to next link in the chain.
  259. 23 this.addErrback(errback || promise);
  260. 23 return promise.promise();
  261. },
  262. /**
  263. * Applies the same function that returns a promise to both the callback and errback.
  264. *
  265. * @param {Function} callback function to call. This function must return a Promise
  266. *
  267. * @return {comb.Promise} a promise to continue chaining or to resolve with.
  268. *
  269. */
  270. chainBoth:function (callback) {
  271. 6 var promise = new Promise();
  272. 6 this.addCallback(function (results) {
  273. 4 when(isFunction(callback) ? callback.call(this, results) : callback).then(promise);
  274. });
  275. 6 this.addErrback(function (results) {
  276. 2 when(isFunction(callback) ? callback.call(this, results) : callback).then(promise);
  277. });
  278. 6 return promise.promise();
  279. },
  280. /**
  281. * Creates an object to that contains methods to listen to resolution but not the "callback" or "errback" methods.
  282. *
  283. * @example
  284. *
  285. * var asyncMethod = function(){
  286. * var ret = new comb.Promise();
  287. * process.nextTick(ret.callback.bind(ret, "hello"));
  288. * return ret.promise();
  289. * }
  290. *
  291. * asyncMethod().callback() //throws error
  292. *
  293. * @return {Object} an object containing "chain", "chainBoth", "promise", "addCallback", "addErrback", "then", "both".
  294. */
  295. promise:function () {
  296. 436 var ret = {
  297. chain:this.chain.bind(this),
  298. chainBoth:this.chainBoth.bind(this),
  299. promise:function () {
  300. 57 return ret;
  301. }
  302. };
  303. 436 ["addCallback", "addErrback", "then", "both", "classic"].forEach(function (action) {
  304. 2180 ret[action] = function () {
  305. 150 this[action].apply(this, arguments);
  306. 150 return ret;
  307. }.bind(this);
  308. }, this);
  309. 436 return ret;
  310. }
  311. }
  312. });
  313. 1var PromiseList = define(Promise, {
  314. instance:{
  315. /** @lends comb.PromiseList.prototype */
  316. /*@private*/
  317. __results:null,
  318. /*@private*/
  319. __errors:null,
  320. /*@private*/
  321. __promiseLength:0,
  322. /*@private*/
  323. __defLength:0,
  324. /*@private*/
  325. __firedLength:0,
  326. normalizeResults:false,
  327. /**
  328. * PromiseList object used for handling a list of Promises
  329. *
  330. * @example
  331. * var myFunc = function(){
  332. * var promise = new Promise();
  333. * //callback the promise after 10 Secs
  334. * setTimeout(hitch(promise, "callback"), 10000);
  335. * return promise.promise();
  336. * }
  337. * var myFunc2 = function(){
  338. * var promises =[];
  339. * for(var i = 0; i < 10; i++){
  340. * promises.push(myFunc);
  341. * }
  342. * //create a new promise list with all 10 promises
  343. * return new PromiseList(promises).promise();
  344. * }
  345. *
  346. * myFunc.then(function(success){}, function(error){})
  347. * //chain promise operations
  348. * myFunc.chain(myfunc).then(function(success){}, function(error){})
  349. *
  350. * myFunc2.then(function(success){}, function(error){})
  351. * //chain promise operations
  352. * myFunc2.chain(myfunc).then(function(success){}, function(error){})
  353. *
  354. * @param {comb.Promise[]} [defs=[]] the list of promises
  355. * @constructs
  356. * @augments comb.Promise
  357. * @memberOf comb
  358. * */
  359. constructor:function (defs, normalizeResults) {
  360. 18 this.__errors = [];
  361. 18 this.__results = [];
  362. 18 this.normalizeResults = base.isBoolean(normalizeResults) ? normalizeResults : false;
  363. 18 this._super(arguments);
  364. 18 if (defs && defs.length) {
  365. 16 this.__defLength = defs.length;
  366. 16 defs.forEach(this.__addPromise, this);
  367. } else {
  368. 2 this.__resolve();
  369. }
  370. },
  371. /**
  372. * Add a promise to our chain
  373. * @private
  374. * @param promise the promise to add to our chain
  375. * @param i the index of the promise in our chain
  376. */
  377. __addPromise:function (promise, i) {
  378. 42 promise.addCallback(hitch(this, function () {
  379. 37 var args = argsToArray(arguments);
  380. 37 args.unshift(i);
  381. 37 this.callback.apply(this, args);
  382. }));
  383. 42 promise.addErrback(hitch(this, function () {
  384. 5 var args = argsToArray(arguments);
  385. 5 args.unshift(i);
  386. 5 this.errback.apply(this, args);
  387. }));
  388. },
  389. /**
  390. * Resolves the promise
  391. * @private
  392. */
  393. __resolve:function () {
  394. 18 if (!this.__fired) {
  395. 18 this.__fired = true;
  396. 18 var cbs = this.__errors.length ? this.__errorCbs : this.__cbs,
  397. len = cbs.length, i,
  398. results = this.__errors.length ? this.__errors : this.__results;
  399. 18 for (i = 0; i < len; i++) {
  400. 5 this.__callNextTick(cbs[i], results);
  401. }
  402. }
  403. },
  404. __callNextTick:function (cb, results) {
  405. 5 process.nextTick(hitch(this, function () {
  406. 5 cb.apply(this, [results]);
  407. }));
  408. },
  409. addCallback:function (cb) {
  410. 16 if (cb) {
  411. 16 if (exports.isPromiseLike(cb)) {
  412. 1 cb = hitch(cb, "callback");
  413. }
  414. 16 if (this.__fired && !this.__errors.length) {
  415. 7 cb.call(this, this.__results);
  416. } else {
  417. 9 this.__cbs.push(cb);
  418. }
  419. }
  420. 16 return this;
  421. },
  422. addErrback:function (cb) {
  423. 16 if (cb) {
  424. 16 if (exports.isPromiseLike(cb)) {
  425. 2 cb = hitch(cb, "errback");
  426. }
  427. 16 if (this.__fired && this.__errors.length) {
  428. 4 cb.call(this, this.__errors);
  429. } else {
  430. 12 this.__errorCbs.push(cb);
  431. }
  432. }
  433. 16 return this;
  434. },
  435. callback:function (i) {
  436. 38 if (this.__fired) {
  437. 1 throw new Error("Already fired!");
  438. }
  439. 37 var args = argsToArray(arguments);
  440. 37 if (this.normalizeResults) {
  441. 20 args = args.slice(1);
  442. 20 args = args.length == 1 ? args.pop() : args;
  443. }
  444. 37 this.__results[i] = args;
  445. 37 this.__firedLength++;
  446. 37 if (this.__firedLength == this.__defLength) {
  447. 11 this.__resolve();
  448. }
  449. 37 return this.promise();
  450. },
  451. errback:function (i) {
  452. 6 if (this.__fired) {
  453. 1 throw new Error("Already fired!");
  454. }
  455. 5 var args = argsToArray(arguments);
  456. 5 if (this.normalizeResults) {
  457. 4 args = args.slice(1);
  458. 4 args = args.length == 1 ? args.pop() : args;
  459. }
  460. 5 this.__errors[i] = args;
  461. 5 this.__firedLength++;
  462. 5 if (this.__firedLength == this.__defLength) {
  463. 5 this.__resolve();
  464. }
  465. 5 return this.promise();
  466. }
  467. }
  468. });
  469. 1exports.Promise = Promise;
  470. 1exports.PromiseList = PromiseList;
  471. 1function isPromiseLike(obj) {
  472. 673 return !isUndefinedOrNull(obj) && (isInstanceOf(obj, Promise) || (isFunction(obj.then)
  473. && isFunction(obj.addCallback) && isFunction(obj.addErrback)));
  474. }
  475. 1base.merge(exports, {
  476. /**
  477. * @lends comb
  478. */
  479. /**
  480. * Tests if an object is like a promise (i.e. it contains then, addCallback, addErrback)
  481. * @param obj object to test
  482. * @function
  483. */
  484. isPromiseLike:isPromiseLike,
  485. /**
  486. * Waits for promise and non promise values to resolve and fires callback or errback appropriately. If you pass in an array of promises
  487. * then it will wait for all promises in the list to resolve.
  488. *
  489. * @example
  490. * var a = "hello";
  491. * var b = new comb.Promise().callback(world);
  492. * comb.when(a, b) => called back with ["hello", "world"];
  493. *
  494. * @param {Anything...} args variable number of arguments to wait for.
  495. * @param {Function} cb the callback function
  496. * @param {Function} eb the errback function
  497. * @returns {comb.Promise} a promise that is fired when all values have resolved
  498. */
  499. when:function (args, cb, eb) {
  500. 97 var args = argsToArray(arguments), p;
  501. 97 eb = base.isFunction(args[args.length - 1]) ? args.pop() : null;
  502. 97 cb = base.isFunction(args[args.length - 1]) ? args.pop() : null;
  503. 97 if (eb && !cb) {
  504. 5 cb = eb;
  505. 5 eb = null;
  506. }
  507. 97 if (!args.length) {
  508. 3 p = new Promise().callback(args);
  509. 94 } else if (args.length == 1) {
  510. 88 args = args.pop();
  511. 88 if (isPromiseLike(args)) {
  512. 57 p = args;
  513. 31 } else if (isArray(args) && args.every(isPromiseLike)) {
  514. 1 p = new PromiseList(args, true);
  515. } else {
  516. 30 p = new Promise().callback(args);
  517. }
  518. } else {
  519. 6 p = new PromiseList(args.map(function (a) {
  520. 12 return exports.isPromiseLike(a) ? a : new Promise().callback(a);
  521. }), true);
  522. }
  523. 97 if (cb) {
  524. 11 p.addCallback(cb);
  525. }
  526. 97 if (eb) {
  527. 6 p.addErrback(eb);
  528. }
  529. 97 return p.promise();
  530. },
  531. /**
  532. * Wraps traditional node style functions with a promise.
  533. * @example
  534. *
  535. * var fs = require("fs");
  536. * var readFile = comb.wrap(fs.readFile, fs);
  537. * readFile(__dirname + "/test.json").then(
  538. * function(buffer){
  539. * console.log(contents);
  540. * },
  541. * funciton(err){
  542. *
  543. * } console.error(err);
  544. * );
  545. *
  546. *
  547. * @param {Function} fn function to wrap
  548. * @param {Object} scope scope to call the function in
  549. *
  550. * @return {Funciton} a wrapped function
  551. */
  552. wrap:function (fn, scope) {
  553. 2 return function () {
  554. 2 var ret = new Promise();
  555. 2 var args = argsToArray(arguments);
  556. 2 args.push(ret.resolve.bind(ret));
  557. 2 fn.apply(scope || this, args);
  558. 2 return ret.promise();
  559. }
  560. },
  561. /**
  562. * Executes a list of items in a serial manner. If the list contains promises then each promise
  563. * will be executed in a serial manner, if the list contains non async items then the next item in the list
  564. * is called.
  565. *
  566. * @example
  567. *
  568. * var asyncAction = function(item, timeout){
  569. * var ret = new comb.Promise();
  570. * setTimeout(comb.hitchIgnore(ret, "callback", item), timeout);
  571. * return ret.promise();
  572. * };
  573. *
  574. * comb.serial([
  575. * comb.partial(asyncAction, 1, 1000),
  576. * comb.partial(asyncAction, 2, 900),
  577. * comb.partial(asyncAction, 3, 800),
  578. * comb.partial(asyncAction, 4, 700),
  579. * comb.partial(asyncAction, 5, 600),
  580. * comb.partial(asyncAction, 6, 500)
  581. * ]).then(function(results){
  582. * console.log(results); // [1,2,3,4,5,6];
  583. * });
  584. *
  585. *
  586. *
  587. * @param list
  588. * @param callback
  589. * @param errback
  590. */
  591. serial:function (list, callback, errback) {
  592. 4 if (base.isArray(list)) {
  593. 3 return callNext(list, 0, [], false, new Promise().then(callback, errback), false)
  594. } else {
  595. 1 throw new Error("When calling comb.serial the first argument must be an array");
  596. }
  597. },
  598. /**
  599. * Works just like {@link comb.Promise#chain} method, allowing you to propogate results from one funciton to another.
  600. * This is different than {@link comb.serial} in that it propogates results from one promise to the next, where
  601. * {@link comb.serial} does not.
  602. *
  603. * @example
  604. *
  605. * function asyncAction(add, timeout) {
  606. * return function (num) {
  607. * num = num || 0;
  608. * var ret = new comb.Promise();
  609. * setTimeout(function () {
  610. * ret.callback(num + add);
  611. * }, timeout);
  612. * return ret;
  613. * }
  614. * }
  615. *
  616. * comb.chain([
  617. * asyncAction(1, 100),
  618. * asyncAction(2, 100),
  619. * asyncAction(3, 100),
  620. * asyncAction(4, 100),
  621. * asyncAction(5, 100),
  622. * ]).then(function(results){
  623. * console.log(results); //15
  624. * });
  625. *
  626. * @param {function[]} list an array of function to call.
  627. * @return {comb.Promise} a promise that will resolve with the results of the last function in the list.
  628. */
  629. chain:function (list) {
  630. 5 if (base.isArray(list)) {
  631. 4 return callNext(list, 0, null, false, new Promise(), true);
  632. } else {
  633. 1 throw new Error("When calling comb.serial the first argument must be an array");
  634. }
  635. }
  636. });
  637. 1var when = exports.when;
  638. 1function callNextSuccess(list, index, results, isErrored, ret, propogate) {
  639. //store results
  640. 51 var args = argsToArray(arguments, 6);
  641. 51 if (propogate) {
  642. 31 process.nextTick(base.partial(callNext, list, index + 1, args, isErrored, ret, propogate));
  643. } else {
  644. 20 results.push(args.length == 1 ? args[0] : args);
  645. 20 process.nextTick(base.partial(callNext, list, index + 1, results, isErrored, ret, propogate));
  646. }
  647. }
  648. 1function callNextError(index, results, isErrored, ret, propogate) {
  649. //store results
  650. 2 var args = argsToArray(arguments, 5);
  651. 2 ret.errback.apply(ret, args);
  652. }
  653. 1function callNext(list, index, results, isErrored, ret, propogate) {
  654. 58 if (index < list.length) {
  655. 55 var item = list[index];
  656. 55 try {
  657. 55 when(base.isFunction(item) ? propogate ? item.apply(null, results) : item() : item).then(
  658. callNextSuccess.bind(null, list, index, results, isErrored, ret, propogate),
  659. callNextError.bind(null, index, results, isErrored, ret, propogate));
  660. } catch (e) {
  661. 2 ret.errback(e);
  662. }
  663. } else {
  664. 3 propogate ? ret.callback.apply(ret, results) : ret.callback(results);
  665. }
  666. 58 return ret.promise();
  667. }