index.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. module.exports = Traverse;
  2. function Traverse (obj) {
  3. if (!(this instanceof Traverse)) return new Traverse(obj);
  4. this.value = obj;
  5. }
  6. Traverse.prototype.get = function (ps) {
  7. var node = this.value;
  8. for (var i = 0; i < ps.length; i ++) {
  9. var key = ps[i];
  10. if (!Object.hasOwnProperty.call(node, key)) {
  11. node = undefined;
  12. break;
  13. }
  14. node = node[key];
  15. }
  16. return node;
  17. };
  18. Traverse.prototype.set = function (ps, value) {
  19. var node = this.value;
  20. for (var i = 0; i < ps.length - 1; i ++) {
  21. var key = ps[i];
  22. if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
  23. node = node[key];
  24. }
  25. node[ps[i]] = value;
  26. return value;
  27. };
  28. Traverse.prototype.map = function (cb) {
  29. return walk(this.value, cb, true);
  30. };
  31. Traverse.prototype.forEach = function (cb) {
  32. this.value = walk(this.value, cb, false);
  33. return this.value;
  34. };
  35. Traverse.prototype.reduce = function (cb, init) {
  36. var skip = arguments.length === 1;
  37. var acc = skip ? this.value : init;
  38. this.forEach(function (x) {
  39. if (!this.isRoot || !skip) {
  40. acc = cb.call(this, acc, x);
  41. }
  42. });
  43. return acc;
  44. };
  45. Traverse.prototype.deepEqual = function (obj) {
  46. if (arguments.length !== 1) {
  47. throw new Error(
  48. 'deepEqual requires exactly one object to compare against'
  49. );
  50. }
  51. var equal = true;
  52. var node = obj;
  53. this.forEach(function (y) {
  54. var notEqual = (function () {
  55. equal = false;
  56. //this.stop();
  57. return undefined;
  58. }).bind(this);
  59. //if (node === undefined || node === null) return notEqual();
  60. if (!this.isRoot) {
  61. /*
  62. if (!Object.hasOwnProperty.call(node, this.key)) {
  63. return notEqual();
  64. }
  65. */
  66. if (typeof node !== 'object') return notEqual();
  67. node = node[this.key];
  68. }
  69. var x = node;
  70. this.post(function () {
  71. node = x;
  72. });
  73. var toS = function (o) {
  74. return Object.prototype.toString.call(o);
  75. };
  76. if (this.circular) {
  77. if (Traverse(obj).get(this.circular.path) !== x) notEqual();
  78. }
  79. else if (typeof x !== typeof y) {
  80. notEqual();
  81. }
  82. else if (x === null || y === null || x === undefined || y === undefined) {
  83. if (x !== y) notEqual();
  84. }
  85. else if (x.__proto__ !== y.__proto__) {
  86. notEqual();
  87. }
  88. else if (x === y) {
  89. // nop
  90. }
  91. else if (typeof x === 'function') {
  92. if (x instanceof RegExp) {
  93. // both regexps on account of the __proto__ check
  94. if (x.toString() != y.toString()) notEqual();
  95. }
  96. else if (x !== y) notEqual();
  97. }
  98. else if (typeof x === 'object') {
  99. if (toS(y) === '[object Arguments]'
  100. || toS(x) === '[object Arguments]') {
  101. if (toS(x) !== toS(y)) {
  102. notEqual();
  103. }
  104. }
  105. else if (x instanceof Date || y instanceof Date) {
  106. if (!(x instanceof Date) || !(y instanceof Date)
  107. || x.getTime() !== y.getTime()) {
  108. notEqual();
  109. }
  110. }
  111. else {
  112. var kx = Object.keys(x);
  113. var ky = Object.keys(y);
  114. if (kx.length !== ky.length) return notEqual();
  115. for (var i = 0; i < kx.length; i++) {
  116. var k = kx[i];
  117. if (!Object.hasOwnProperty.call(y, k)) {
  118. notEqual();
  119. }
  120. }
  121. }
  122. }
  123. });
  124. return equal;
  125. };
  126. Traverse.prototype.paths = function () {
  127. var acc = [];
  128. this.forEach(function (x) {
  129. acc.push(this.path);
  130. });
  131. return acc;
  132. };
  133. Traverse.prototype.nodes = function () {
  134. var acc = [];
  135. this.forEach(function (x) {
  136. acc.push(this.node);
  137. });
  138. return acc;
  139. };
  140. Traverse.prototype.clone = function () {
  141. var parents = [], nodes = [];
  142. return (function clone (src) {
  143. for (var i = 0; i < parents.length; i++) {
  144. if (parents[i] === src) {
  145. return nodes[i];
  146. }
  147. }
  148. if (typeof src === 'object' && src !== null) {
  149. var dst = copy(src);
  150. parents.push(src);
  151. nodes.push(dst);
  152. Object.keys(src).forEach(function (key) {
  153. dst[key] = clone(src[key]);
  154. });
  155. parents.pop();
  156. nodes.pop();
  157. return dst;
  158. }
  159. else {
  160. return src;
  161. }
  162. })(this.value);
  163. };
  164. function walk (root, cb, immutable) {
  165. var path = [];
  166. var parents = [];
  167. var alive = true;
  168. return (function walker (node_) {
  169. var node = immutable ? copy(node_) : node_;
  170. var modifiers = {};
  171. var state = {
  172. node : node,
  173. node_ : node_,
  174. path : [].concat(path),
  175. parent : parents.slice(-1)[0],
  176. key : path.slice(-1)[0],
  177. isRoot : path.length === 0,
  178. level : path.length,
  179. circular : null,
  180. update : function (x) {
  181. if (!state.isRoot) {
  182. state.parent.node[state.key] = x;
  183. }
  184. state.node = x;
  185. },
  186. 'delete' : function () {
  187. delete state.parent.node[state.key];
  188. },
  189. remove : function () {
  190. if (Array.isArray(state.parent.node)) {
  191. state.parent.node.splice(state.key, 1);
  192. }
  193. else {
  194. delete state.parent.node[state.key];
  195. }
  196. },
  197. before : function (f) { modifiers.before = f },
  198. after : function (f) { modifiers.after = f },
  199. pre : function (f) { modifiers.pre = f },
  200. post : function (f) { modifiers.post = f },
  201. stop : function () { alive = false }
  202. };
  203. if (!alive) return state;
  204. if (typeof node === 'object' && node !== null) {
  205. state.isLeaf = Object.keys(node).length == 0;
  206. for (var i = 0; i < parents.length; i++) {
  207. if (parents[i].node_ === node_) {
  208. state.circular = parents[i];
  209. break;
  210. }
  211. }
  212. }
  213. else {
  214. state.isLeaf = true;
  215. }
  216. state.notLeaf = !state.isLeaf;
  217. state.notRoot = !state.isRoot;
  218. // use return values to update if defined
  219. var ret = cb.call(state, state.node);
  220. if (ret !== undefined && state.update) state.update(ret);
  221. if (modifiers.before) modifiers.before.call(state, state.node);
  222. if (typeof state.node == 'object'
  223. && state.node !== null && !state.circular) {
  224. parents.push(state);
  225. var keys = Object.keys(state.node);
  226. keys.forEach(function (key, i) {
  227. path.push(key);
  228. if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
  229. var child = walker(state.node[key]);
  230. if (immutable && Object.hasOwnProperty.call(state.node, key)) {
  231. state.node[key] = child.node;
  232. }
  233. child.isLast = i == keys.length - 1;
  234. child.isFirst = i == 0;
  235. if (modifiers.post) modifiers.post.call(state, child);
  236. path.pop();
  237. });
  238. parents.pop();
  239. }
  240. if (modifiers.after) modifiers.after.call(state, state.node);
  241. return state;
  242. })(root).node;
  243. }
  244. Object.keys(Traverse.prototype).forEach(function (key) {
  245. Traverse[key] = function (obj) {
  246. var args = [].slice.call(arguments, 1);
  247. var t = Traverse(obj);
  248. return t[key].apply(t, args);
  249. };
  250. });
  251. function copy (src) {
  252. if (typeof src === 'object' && src !== null) {
  253. var dst;
  254. if (Array.isArray(src)) {
  255. dst = [];
  256. }
  257. else if (src instanceof Date) {
  258. dst = new Date(src);
  259. }
  260. else if (src instanceof Boolean) {
  261. dst = new Boolean(src);
  262. }
  263. else if (src instanceof Number) {
  264. dst = new Number(src);
  265. }
  266. else if (src instanceof String) {
  267. dst = new String(src);
  268. }
  269. else {
  270. dst = Object.create(Object.getPrototypeOf(src));
  271. }
  272. Object.keys(src).forEach(function (key) {
  273. dst[key] = src[key];
  274. });
  275. return dst;
  276. }
  277. else return src;
  278. }