Last active
November 22, 2017 23:42
-
-
Save enten/8912278c109147a3f9a3f05390c63efc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Usage */ | |
const Accessor = require('./accessor') | |
const obj = {} | |
Accessor.in(obj).under('options') | |
.add('foo.bar', () => 'fallback foobar') | |
//.add('what.you.need', () => 'fallback value') | |
// { defaultFooBar: [Function: value], | |
// getFooBar: [Function: value], | |
// hasFooBar: [Function: value], | |
// setFooBar: [Function: value] } | |
// unsetFooBar: [Function: value], | |
obj.getFooBar() | |
// fallback foobar | |
obj.hasFooBar() | |
// false | |
obj.defaultFooBar('default foobar') | |
// { defaultFooBar: [Function: value], | |
// getFooBar: [Function: value], | |
// hasFooBar: [Function: value], | |
// setFooBar: [Function: value], | |
// unsetFooBar: [Function: value], | |
// options: { foo: { bar: 'default foobar' } } } | |
obj.hasFooBar() | |
// true | |
obj.setFooBar('my foobar') | |
// { defaultFooBar: [Function: value], | |
// getFooBar: [Function: value], | |
// hasFooBar: [Function: value], | |
// setFooBar: [Function: value], | |
// unsetFooBar: [Function: value], | |
// options: { foo: { bar: 'my foobar' } } } | |
obj.defaultFooBar('ignored value because "foo.bar" exists') | |
// { defaultFooBar: [Function: value], | |
// getFooBar: [Function: value], | |
// hasFooBar: [Function: value], | |
// setFooBar: [Function: value], | |
// unsetFooBar: [Function: value], | |
// options: { foo: { bar: 'my foobar' } } } | |
obj.getFooBar() | |
// my foobar | |
obj.unsetFooBar() | |
// { defaultFooBar: [Function: value], | |
// getFooBar: [Function: value], | |
// hasFooBar: [Function: value], | |
// setFooBar: [Function: value], | |
// unsetFooBar: [Function: value], | |
// options: { foo: {} } } | |
/** Module */ | |
function accessor (name, fallback) { | |
return { | |
default: (obj, value) => defaultIn(obj, name, value), | |
get: (obj, userFallback) => getIn(obj, name, userFallback || fallback), | |
has: (obj) => hasIn(obj, name), | |
set: (value) => setIn(obj, name, value), | |
unset: () => unsetIn(obj, name) | |
} | |
} | |
function createAccessor (obj, name, fallback, optionsKey) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
Object.keys(name).forEach((key) => createAccessor(obj, key, name[key], fallback || optionsKey)) | |
return obj | |
} | |
if (typeof name === 'string') { | |
name = name.split('.') | |
} | |
if (typeof optionsKey === 'string') { | |
optionsKey = optionsKey.split('.') | |
} | |
const nameFirstChar = name[0][0] | |
const nameUcFirst = name.reduce((acc, part) => acc + part[0].toUpperCase() + part.substring(1), '') | |
name = [].concat(optionsKey || [], name) | |
const getter = function (userFallback) { | |
return getIn(this, name, userFallback || fallback) | |
} | |
const setter = function (value) { | |
return setIn(this, name, value) | |
} | |
Object.defineProperties(obj, { | |
[`default${nameUcFirst}`]: { | |
configurable: true, | |
enumerable: false, | |
value (value) { | |
return defaultIn(this, name, value) | |
}, | |
writable: true | |
}, | |
[`get${nameUcFirst}`]: { | |
configurable: true, | |
enumerable: false, | |
value: getter, | |
writable: true | |
}, | |
[`has${nameUcFirst}`]: { | |
configurable: true, | |
enumerable: false, | |
value () { | |
return hasIn(this, name) | |
}, | |
writable: true | |
}, | |
[`set${nameUcFirst}`]: { | |
configurable: true, | |
enumerable: false, | |
value: setter, | |
writable: true | |
}, | |
[`unset${nameUcFirst}`]: { | |
configurable: true, | |
enumerable: false, | |
value () { | |
return unsetIn(this, name) | |
}, | |
writable: true | |
} | |
}) | |
if (optionsKey) { | |
Object.defineProperty(obj, nameFirstChar + nameUcFirst.substring(1), { | |
configurable: true, | |
enumerable: true, | |
get: getter, | |
set: setter | |
}) | |
} | |
return obj | |
} | |
function createAccessorBuilder (options = {}) { | |
const add = ({obj, name, fallback, optionsKey}) => { | |
return createAccessor(obj, name, fallback, optionsKey) | |
} | |
if (options.obj && options.name) { | |
return add(options) | |
} | |
return { | |
add (name, fallback) { | |
add(Object.assign({}, | |
options, | |
{name}, | |
arguments.length > 1 && {fallback} | |
)) | |
return this | |
}, | |
default (fallback) { | |
options = Object.assign({}, options, {fallback}) | |
return createAccessorBuilder(options) | |
}, | |
in (obj) { | |
options = Object.assign({}, options, {obj}) | |
if (arguments.length > 1) { | |
options.optionsKey = arguments[1] | |
} | |
return createAccessorBuilder(options) | |
}, | |
under (optionsKey) { | |
options = Object.assign({}, options, {optionsKey}) | |
if (arguments.length > 1) { | |
options.obj = arguments[1] | |
} | |
return createAccessorBuilder(options) | |
} | |
} | |
} | |
function defaultIn (obj, name, value) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
Object.keys(name).forEach((key) => defaultIn(obj, key, name[key])) | |
return obj | |
} | |
if (!getIn(obj, name)) { | |
setIn(obj, name, value) | |
} | |
return obj | |
} | |
function getIn (obj, name, fallback) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
return Object.keys(name).reduce((acc, key) => { | |
acc[key] = getIn(obj, key, name[key]) | |
return acc | |
}, {}) | |
} | |
obj = walkIn(obj, name) | |
if (obj != null) { | |
return obj | |
} | |
if (typeof fallback === 'function') { | |
return fallback(obj, name) | |
} | |
return fallback | |
} | |
function hasIn (obj, name) { | |
return walkIn(obj, name) != null | |
} | |
function setIn (obj, name, value) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
Object.keys(name).forEach((key) => setIn(obj, key, name[key])) | |
return obj | |
} | |
if (typeof name === 'string') { | |
name = name.split('.') | |
} | |
name = [].concat(name) | |
const lastKey = name.pop() | |
const lastObj = walkIn(obj, name, {parents: true}) | |
lastObj[lastKey] = value | |
return obj | |
} | |
function unsetIn (obj, name) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
Object.keys(name).forEach((key) => setIn(obj, key, name[key])) | |
return obj | |
} | |
if (typeof name === 'string') { | |
name = name.split('.') | |
} | |
name = [].concat(name) | |
const lastKey = name.pop() | |
const lastObj = walkIn(obj, name, {parents: true}) | |
if (lastObj && lastObj.hasOwnProperty(lastKey)) { | |
delete lastObj[lastKey] | |
} | |
return obj | |
} | |
function walkIn (obj, name, {parents} = {}) { | |
if (name && typeof name === 'object' && !Array.isArray(name)) { | |
return Object.keys(name).reduce((acc, key) => { | |
acc[key] = walkIn(obj, key, parents) | |
return acc | |
}, {}) | |
} | |
if (typeof name === 'string') { | |
name = name.split('.') | |
} | |
obj = name.reduce((acc, key) => { | |
if (acc && !acc[key] && parents) { | |
acc[key] = {} | |
} | |
return acc && acc[key] | |
}, obj) | |
return obj | |
} | |
module.exports = Object.assign(createAccessorBuilder(), { | |
accessor, | |
createAccessor, | |
createAccessorBuilder, | |
defaultIn, | |
getIn, | |
hasIn, | |
setIn, | |
unsetIn, | |
walkIn | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment