import {curry, filter, keys, is, omit, pick, pipe, reject} from 'ramda'
import evolveSpec from './evolveSpec'
import {objectify, makePruningSpec} from './util'
/**
* Keeps only the props you name in your spec.
* It is expected (best practice, for conveying intent) that you give a value of `true` for props that you want to keep
* but _not_ transform (a zipped object - where key and value are both strings of the same name - makes sense too).
*
* @func
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are `true` or match the key name
* to signal a value to be preserved (as-is) on the output
* @param {Object} input An object which will be passed through the pruning defined by the spec
* @returns {Object} The input object with the unnamed props shaved off the output
*/
export const keeper = curry((spec, input) =>
pipe(
objectify,
pipe(makePruningSpec, keys, pick)(spec)
)(input)
)
/**
* Keeps only the props you name in your spec. If you provided a function, it is applied at the prop-level.
* It is expected (best practice, for conveying intent) that you give a value of `true` for props that you want to keep
* but _not_ transform (a zipped object - where key and value are both strings of the same name - makes sense too).
* In this mode, if you provide any other type of value it won't be used in the spec.
* If the prop _doesn't_ exist, you won't see it on the output
* (use shapeSuperStrictly if you want to see `undefined` for those cases instead).
*
* @func
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are either transform functions or (preferrably)
* a value of `true` to signal a value to be preserved (as-is) on the output
* @param {Object} input An object which will be passed through the pruning/re-shaping defined by the spec
* @returns {Object} The input object with the spec transforms applied to it and
* unnamed props shaved off the output
*/
export const keepAndShape = curry((spec, input) =>
pipe(
keeper(spec),
evolveSpec(filter(is(Function), spec))
)(input)
)
/**
* Removes the props you name in your spec.
* It is expected (best practice, for conveying intent) that you give a value of `true` for props that you want to remove
* but _not_ transform (a zipped object - where key and value are both strings of the same name - makes sense too).
*
* @func
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are `true` or match the key name
* to signal a value to be removed from the output
* @param {Object} input An object which will be passed through the pruning defined by the spec
* @returns {Object} The input object with the named props shaved off the output
*/
export const remover = curry((spec, input) =>
pipe(
objectify,
pipe(makePruningSpec, reject(is(Function)), keys, omit)(spec)
)(input)
)
/**
* Removes all the props you name in your spec. If you provided a transform function however,
* it is applied at the prop-level and that prop is (obviously) NOT removed but rather transformed accordingly.
* It is expected (best practice, for conveying intent) that you give a value of `true` for props you want to remove
* (a zipped object - where key and value are both strings of the same name - makes sense too).
* In this mode, if you provide any other type of value it won't be used in the spec.
*
* @func
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are either transform functions or (preferrably)
* a value of `true` to signal a value to be remove from the output
* @param {Object} input An object which will be passed through the pruning/re-shaping defined by the spec
* @returns {Object} The input object with the spec transforms applied to it and
* name props shaved off the output
*/
export const removeAndShape = curry((spec, input) =>
pipe(
remover(spec),
evolveSpec(filter(is(Function), spec))
)(input)
)
/**
* Removes the any props from the input not named in the spec. Doesn't matter
* what the value types are in the spec: if the key name doesn't exist in the
* spec, it won't exist in the output.
*
* @func
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object
* @param {Object} input An object which will have any props not named in the spect removed
* @returns {Object} The input object with the props removed which were not in the spec too
*/
export const impliedRemove = curry((spec, input) => pick(keys(spec), input))