Skip to content

Instantly share code, notes, and snippets.

@enten
Last active November 22, 2017 23:42
Show Gist options
  • Save enten/8912278c109147a3f9a3f05390c63efc to your computer and use it in GitHub Desktop.
Save enten/8912278c109147a3f9a3f05390c63efc to your computer and use it in GitHub Desktop.
/** 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