/**
* This file is part of SesaJsTools.
* Copyright 2014-2015, Association Sésamath
*
* SesaJsTools is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation.
*
* SesaJsTools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SesaJsTools (LICENCE.txt).
* @see http://www.gnu.org/licenses/agpl.txt
*
*
* Ce fichier fait partie de l'application Sésathèque, créée par l'association Sésamath.
*
* Sésathèque est un logiciel libre ; vous pouvez le redistribuer ou le modifier suivant
* les termes de la GNU Affero General Public License version 3 telle que publiée par la
* Free Software Foundation.
* Sésathèque est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE,
* sans même la garantie tacite de QUALITÉ MARCHANDE ou d'ADÉQUATION à UN BUT PARTICULIER.
* Consultez la GNU Affero General Public License pour plus de détails.
* Vous devez avoir reçu une copie de la GNU General Public License en même temps que Sésathèque
* (cf LICENCE.txt et http://vvlibri.org/fr/Analyse/gnu-affero-general-public-license-v3-analyse
* pour une explication en français)
*/
'use strict'
var isEqual = require('lodash.isequal')
var tools = require('../index')
var hasProp = tools.hasProp
/**
* Clone un objet en conservant son prototype
* @param object
* @returns {Object}
*/
function clone (object) {
var copy = object
if (Array.isArray(object)) {
copy = object.slice()
} else if (object instanceof Date) {
copy = new Date(object)
} else if (object instanceof RegExp) {
copy = new RegExp(object)
} else if (object instanceof Object) {
copy = Object.create(Object.getPrototypeOf(object))
update(copy, object)
}
return copy
}
/**
* Clone les datas d'un objet (avec stringify et parse, ça vire les méthodes,
* Date revient en string, RexExp en {})
* @param {Object} object
* @returns {Object}
*/
function cloneData (object) {
return tools.parse(tools.stringify(object))
}
/**
* Update object en y ajoutant toutes les propriétés de default qui n'existait pas dans object sans modifier les autres
* @param {object} object
* @param {object} defaultValues
* @param {boolean} [recursion=true] Passer false pour ne compléter que les propriétés 'racine' de l'objet sans récursion
*/
function complete (object, defaultValues, recursion) {
// recursion=true par défaut
if (recursion !== false) recursion = true
function completeObj (obj, values) {
var value
for (var key in values) {
if (hasProp(values, key)) {
value = values[key]
if (!hasProp(obj, key)) obj[key] = value
else if (recursion && tools.isObject(obj[key]) && tools.isObject(value)) completeObj(obj[key], value)
}
}
}
if (tools.isObject(object) && tools.isObject(defaultValues)) completeObj(object, defaultValues)
}
/**
* Fusionne les nouvelles valeurs avec les propriétés de l'objet (en profondeur)
* (concatène si les deux propriétés sont des tableaux, en virant d'éventuels doublons,
* fusionne si c'est deux objets en écrasant les propriétés de object par celles de newValues)
* @param {Object} object L'objet source
* @param {Object} newValues Les valeurs à fusionner
* @param {boolean} [strict=false] passer true pour lancer une exception si les arguments ne sont pas 2 Object ou 2 Array
*/
function merge (object, newValues, strict) {
function mergeArray (arDest, arSrc) {
var s, d, found
for (s = 0; s < arSrc.length; s++) {
found = false
for (d = 0; d < arDest.length; d++) {
if (isEqual(arSrc[s], arDest[d])) {
found = true
break
}
}
if (!found) arDest.push(arSrc[s])
}
}
function mergeObj (obj, values) {
var value
for (var key in values) {
if (hasProp(values, key)) {
value = values[key]
// 2 tableaux à merger
if (tools.isArray(value) && tools.isArray(obj[key])) mergeArray(obj[key], value)
// 2 objets
else if (tools.isObject(value) && tools.isObject(obj[key])) mergeObj(obj[key], value)
// sinon on écrase
else obj[key] = value
}
}
}
if (Array.isArray(object) && Array.isArray(newValues)) mergeArray(object, newValues)
else if (object && newValues && tools.isObject(object) && tools.isObject(newValues)) mergeObj(object, newValues)
else if (strict) throw new Error('merge réclame 2 Object ou 2 Array')
}
/**
* Retourne un plain object avec les propriétés de obj demandées (shallow copy)
* @param obj
* @param …
* @returns {object}
*/
function pick (obj) {
if (arguments.length < 2) {
console.error(new Error('pick appelé sans propriétés à prendre'), obj)
return {}
}
if (typeof obj !== 'object') {
console.error(new Error('Le premier argument de pick n’est pas un objet'), obj)
obj = {}
}
var propsToPick = Array.prototype.slice.call(arguments, 1)
var retour = {}
propsToPick.forEach(function (prop) {
if (typeof prop === 'string') retour[prop] = obj[prop]
})
return retour
}
/**
* Modifie toutes les propriétés de obj pour qu'elles deviennent identique à celles de dest,
* (en virant les propriétés superflues), mais sans affecter directement obj
* (pour garder la référence à un objet affecté à cette variable précédemment)
* Tout le contraire d'immutable en résumé…
* @param {Object} object
* @param {Object} dest
*/
function replace (obj, dest) {
// on màj les propriétés qui existent dans dest
update(obj, dest)
// et on vire les autres
Object.getOwnPropertyNames(obj).forEach(function (prop) {
if (!hasProp(dest, prop)) delete obj[prop]
})
}
/**
* Retourne la liste des propriétés vraies (truthy) d'un objet
* @param {object} obj
* @returns {Array}
*/
function truePropertiesList (obj) {
var list = []
if (typeof obj === 'object') {
for (var prop in obj) {
if (hasProp(obj, prop) && obj.prop) list.push(prop)
}
}
return list
}
/**
* Update object en y ajoutant toutes les propriétés de addition
* @param object
* @param addition
*/
function update (object, addition) {
Object.getOwnPropertyNames(addition).forEach(function (property) {
// on ajoute ou met à jour la propriété avec son descripteur complet
Object.defineProperty(
object,
property,
Object.getOwnPropertyDescriptor(addition, property)
)
})
}
/**
* Update object en y mettant à jour ses propriétés par celles de values qu'ils ont en commun
* (les propriétés en plus de values sont ignorées)
* @param {object} object
* @param {object} values
*/
function updateIfExists (object, values) {
Object.getOwnPropertyNames(values).forEach(function (property) {
if (hasProp(object, property)) {
// on met à jour la propriété avec son descripteur complet
Object.defineProperty(
object,
property,
Object.getOwnPropertyDescriptor(values, property)
)
}
})
}
/**
* Collection de fonctions génériques pour manipuler un object
* @service sesajstools/utils/object
*/
module.exports = {
clone: clone,
cloneData: cloneData,
complete: complete,
isEqual: isEqual,
merge: merge,
pick: pick,
replace: replace,
truePropertiesList: truePropertiesList,
update: update,
updateIfExists: updateIfExists
}