src/configMapping.ts
// Copyright (C) 2017-2019 Brainbean Apps OU (https://brainbeanapps.com).
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import { forOwn, clone, isRegExp } from 'lodash'
import check from 'check-types'
export default class ConfigMapping {
/**
* @constructor
* @param that Other mapping to clone
*/
constructor(that?: Object) {
if (that) {
forOwn(that, (declaration, selector) => {
this[selector] = clone(declaration)
})
}
}
/**
* Returns copy of this mapping, extended using specified modifier
*
* @param modifier An modifier functor
* @return Copy of this mapping
*/
extended(modifier?: Function) {
const clone = new ConfigMapping(this)
return modifier ? modifier(clone) : clone
}
/**
* Remaps a parameter matched to specified selector using specified mapper
*
* @param selector Parameter selector, supports regexp
* @param mapper Mapper function
* @return This instance (for chaining purposes)
*/
remap(selector: string | RegExp, mapper: Function) {
check.assert(
check.any([check.instance(selector, RegExp), check.not.null(selector)]),
)
// Convert regexp to a string, if needed
if (isRegExp(selector)) {
// @ts-ignore
selector = selector.toString().match(/^\/(.*?)\/[gimuy]?$/)[1]
if (!selector) {
throw new Error(`'${selector}' selector is not valid Regexp`)
}
}
if (this[selector] && this[selector].mapper) {
throw new Error(`'${selector}' selector already mapped`)
}
this[selector] = Object.assign({}, this[selector], {
mapper,
})
return this
}
/**
* Renames a parameter
*
* @param selector Parameter selector, supports regexp
* @param newName New name for matching parameter
* @return This instance (for chaining purposes)
*/
rename(selector: string, newName: string) {
check.assert.nonEmptyString(newName)
return this.remap(selector, (value: any) => {
if (!value) {
return
}
let mapped = {}
mapped[newName] = value
return mapped
})
}
/**
* Uses parameter as-is
*
* @param selector Parameter selector, supports regexp
* @return This instance (for chaining purposes)
*/
asIs(selector: string) {
return this.remap(selector, (value: any, currentName: string) => {
if (!value) {
return
}
let mapped = {}
mapped[currentName] = value
return mapped
})
}
/**
* Sets default value factory for the selector
*
* @param selector Parameter selector, supports regexp
* @param defaultValueFactory Default value factory
* @return This instance (for chaining purposes)
*/
default(selector: string, defaultValueFactory: Function) {
// TODO: check selector type
if (this[selector] && this[selector].defaultValueFactory) {
throw new Error(
`'${selector}' selector already has default value factory`,
)
}
this[selector] = Object.assign({}, this[selector], {
defaultValueFactory,
})
return this
}
/**
* Applies mappings to given values
*
* @param values Values to map
* @return Mapped values
*/
apply(values: Object) {
let mappedValues = {}
forOwn(this, (declaration: any, selector) => {
let wasMatched = false
if (declaration.mapper) {
let matched = 0
forOwn(values, (parameterValue, parameter) => {
if (!parameter.match(new RegExp(selector))) {
return
}
const mappedValue = declaration.mapper(
parameterValue,
parameter,
values,
)
if (mappedValue === undefined) {
return
}
mappedValues = Object.assign({}, mappedValues, mappedValue)
matched++
})
wasMatched = matched > 0
}
if (!wasMatched && declaration.defaultValueFactory) {
const defaultValue = declaration.defaultValueFactory()
if (defaultValue) {
let defaultValueObject = {}
defaultValueObject[selector] = defaultValue
mappedValues = Object.assign({}, mappedValues, defaultValueObject)
}
}
})
return mappedValues
}
}