index.js 8.7 KB

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