From 9fff017c37d5099e03ded71c5a5309d83002f207 Mon Sep 17 00:00:00 2001 From: Remy Sharp Date: Tue, 23 Aug 2016 19:39:29 +0100 Subject: [PATCH] feat: support `*` in paths i.e. undefsafe([ { a: 1 }, { b: 2} ], '*.a') === 1 --- lib/undefsafe.js | 30 +++++++++++++++++++++---- package.json | 6 ++++- test/star-rule.test.js | 50 ++++++++++++++++++++++++++++++++++++++++++ test/undefsafe.test.js | 2 +- 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 test/star-rule.test.js diff --git a/lib/undefsafe.js b/lib/undefsafe.js index 6df2a45..c9d0204 100644 --- a/lib/undefsafe.js +++ b/lib/undefsafe.js @@ -4,8 +4,11 @@ function undefsafe(obj, path, value) { var parts = path.split('.'); var key = null; var type = typeof obj; + var root = obj; var parent = obj; + var star = parts.filter(function (_) { return _ === '*' }).length > 0; + // we're dealing with a primative if (type !== 'object' && type !== 'function') { return obj; @@ -13,8 +16,28 @@ function undefsafe(obj, path, value) { return obj; } - while ((key = parts.shift())) { + key = parts[0]; + var i = 0; + for (; i < parts.length; i++) { + key = parts[i]; parent = obj; + + if (key === '*') { + // loop through each property + var prop = ''; + + for (prop in parent) { + var shallowObj = undefsafe(obj[prop], parts.slice(i + 1).join('.'), value); + if (shallowObj) { + if ((value && shallowObj === value) || (!value)) { + return shallowObj; + } + } + } + return undefined; + key = prop; + } + obj = obj[key]; if (obj === undefined || obj === null) { break; @@ -23,13 +46,12 @@ function undefsafe(obj, path, value) { // if we have a null object, make sure it's the one the user was after, // if it's not (i.e. parts has a length) then give undefined back. - if (obj === null && parts.length !== 0) { + if (obj === null && i !== parts.length - 1) { obj = undefined; - } else if (value) { + } else if (!star && value) { key = path.split('.').pop(); parent[key] = value; } - return obj; } diff --git a/package.json b/package.json index 978e583..cde1d7e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,10 @@ "license": "MIT", "devDependencies": { "semantic-release": "^4.3.5", - "tap": "^5.7.1" + "tap": "^5.7.1", + "tap-only": "0.0.5" + }, + "dependencies": { + "debug": "^2.2.0" } } diff --git a/test/star-rule.test.js b/test/star-rule.test.js new file mode 100644 index 0000000..3b6dc45 --- /dev/null +++ b/test/star-rule.test.js @@ -0,0 +1,50 @@ +// process.stdout.write('\033c'); // clear the screen +var test = require('tap-only'); +var undefsafe = require('../lib/undefsafe'); +var fixture = { + "commits": [ + { + "modified": [ + "one", + "two" + ] + }, + { + "modified": [ + "two", + "four" + ] + } + ] +}; + +test('get value on first * selector', function (t) { + var res = undefsafe(fixture, 'commits.*.modified.*'); + t.equal(res, 'one'); + t.end(); +}); + +test('walking multiple routes', function (t) { + var res = undefsafe(fixture, 'commits.*.modified.*', 'four'); + t.equal(res, 'four'); + t.end(); +}); + + +test('get specific match * selector', function (t) { + var res = undefsafe(fixture, 'commits.*.modified.*', 'two'); + t.equal(res, 'two'); + t.end(); +}); + +test('match * selector returns undefined', function (t) { + var res = undefsafe(fixture, 'commits.*.modified.*', 'three'); + t.equal(res, undefined); + t.end(); +}); + +test('match * selector works on objects', function (t) { + var res = undefsafe(fixture, '*.*.modified.*'); + t.equal(res, 'one'); + t.end(); +}); diff --git a/test/undefsafe.test.js b/test/undefsafe.test.js index 6ec3ce7..8ef512e 100644 --- a/test/undefsafe.test.js +++ b/test/undefsafe.test.js @@ -1,5 +1,5 @@ 'use strict'; -var test = require('tap').test; +var test = require('tap-only'); var undefsafe = require('../lib/undefsafe'); test('should handle primatives', function (t) {